Skip to content

Commit

Permalink
improvements, password test and wip salesman
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Hohler committed Apr 16, 2018
1 parent c4bb1d8 commit 56b45be
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 117 deletions.
54 changes: 54 additions & 0 deletions src/demos/findPassword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Selection } from "../genetic/methods";
import { Candidate, Population } from "../genetic/model";

export function findPassword(password: string) {

const fitness = (pass: string) => (genes: string[]) => {
const passwordArray = pass.split("");
if (genes.length !== pass.length) {
throw Error("passwords length not matching");
}
let score = 0;
for (let i = 0; i < genes.length; i++) {
if (genes[i] === passwordArray[i]) {
score += 1;
}
}
return score * 100 / pass.length;
};

const mutate = (pass: string) => (genes: string[]) => {
const genesCopy = genes.slice();
const mutatedIndex = Math.floor(Math.random() * pass.length);
genesCopy[mutatedIndex] = getRandomLetter();
return genesCopy;
};

const generateRandomGenes = (pass: string) => () => {
const genes = [];
for (let i = 0; i < pass.length; i++) {
genes.push(getRandomLetter());
}
return genes;
};

let pop = Population.generatePopulation(
100,
generateRandomGenes(password),
{
fitness: fitness(password),
mutate: mutate(password),
mutationProbability: 0.4
}
);

for (let i = 0; i < 100; i++) {
document.getElementById("main")!.innerHTML
+= ("<br/><br />" + pop.candidates[0].genes + ", fitness: " + pop.candidates[0].fitness(pop.candidates[0].genes));
pop = pop.createNextGeneration();
}

function getRandomLetter() {
return String.fromCharCode(97 + Math.floor(Math.random() * 26));
}
}
70 changes: 70 additions & 0 deletions src/demos/salesman.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Selection } from "../genetic/methods";
import { Candidate, Population } from "../genetic/model";

export interface Position {
x: number;
y: number;
}

export function salesman(towns: Position[]) {

const fitness = (genes: number[]) => {
const totalDistance = genes.reduce((acc, gene, index) => {
if (index === 0) {
return 0;
}
return acc + distance(towns[gene], towns[genes[index - 1]]);
});
return 1 / totalDistance;
};

const mutate = (genes: number[]) => {
const genesCopy = genes.slice();
const mutatedIndex1 = Math.floor(Math.random() * genes.length);
const mutatedIndex2 = Math.floor(Math.random() * genes.length);
[genesCopy[mutatedIndex1], genesCopy[mutatedIndex2]] = [genesCopy[mutatedIndex2], genesCopy[mutatedIndex1]];
return genesCopy;
};

const cross = (motherGenes: number[], fatherGenes: number[]) => {
const startPos = 0;
}

let pop = Population.generatePopulation(
20,
createRandomGenotype,
{
fitness,
mutate,
cross,
mutationProbability: 0.4
}
);

for (let i = 0; i < 40; i++) {
document.getElementById("main")!.innerHTML
+= ("<br/><br />" + pop.candidates[0].genes + ", fitness: " + pop.candidates[0].fitness(pop.candidates[0].genes));
pop = pop.createNextGeneration();
}

function createRandomGenotype() {
const genotype = Array.from({length: towns.length}, (v, k) => k);
shuffleArray(genotype);
return genotype;
}
}

function distance(pos1: Position, pos2: Position) {
return Math.sqrt(Math.pow(pos2.x - pos1.x, 2) + Math.pow(pos2.y - pos1.y, 2));
}

/**
* Randomize array element order in-place.
* Using Durstenfeld shuffle algorithm.
*/
function shuffleArray(array: any[]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
19 changes: 19 additions & 0 deletions src/genetic/methods/crossover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export class Crossover {
static SINGLE_POINT = (crossProbability: number = 0.8) => <T>(motherGenes: T[], fatherGenes: T[]) => {
const firstChildGenes: T[] = [];
const secondChildGenes: T[] = [];
/**
* Cross the two parents genes according to the cross probability
*/
for (let i = 0; i < motherGenes.length ; i++) {
firstChildGenes.push(Math.random() < crossProbability ? motherGenes[i] : fatherGenes[i]);
secondChildGenes.push(Math.random() < crossProbability ? fatherGenes[i] : motherGenes[i]);
}

return [
firstChildGenes,
secondChildGenes
];

}
}
41 changes: 1 addition & 40 deletions src/genetic/model/candidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,20 @@ import { Gene } from "./gene";

interface CandidateOptions<T> {
fitness: (genes: T[]) => number;
mutate: (genes: T[]) => T[];
crossProbability?: number;
mutationProbability?: number;
}

export class Candidate<T extends Gene> {
genes: T[];
fitness: (genes: T[]) => number;
mutate: (genes: T[]) => T[];
crossProbability: number;
mutationProbability: number;

constructor(genes: T[], options: CandidateOptions<T>) {
this.genes = genes;
this.fitness = options.fitness;
this.mutate = options.mutate;
this.crossProbability = options.crossProbability || 0.8;
this.mutationProbability = options.mutationProbability || 0.2;
}

cross = (other: Candidate<T>) => {
let firstChildGenes: T[] = [];
let secondChildGenes: T[] = [];
/**
* Cross the two parents genes according to the cross probability
*/
for (let i = 0; i < this.genes.length ; i++) {
firstChildGenes.push(Math.random() < this.crossProbability ? this.genes[i] : other.genes[i]);
secondChildGenes.push(Math.random() < this.crossProbability ? other.genes[i] : this.genes[i]);
}

/**
* Each child may mutate according to the mutation probability
*/
if (Math.random() < this.mutationProbability) {
firstChildGenes = this.mutate(firstChildGenes);
}
if (Math.random() < this.mutationProbability) {
secondChildGenes = this.mutate(secondChildGenes);
}

return [
new Candidate(firstChildGenes, this.getOptions()),
new Candidate(secondChildGenes, this.getOptions())
];
}

getOptions = (): CandidateOptions<T> => {
return {
fitness: this.fitness,
mutate: this.mutate,
crossProbability: this.crossProbability,
mutationProbability: this.mutationProbability
fitness: this.fitness
};
}
}
37 changes: 22 additions & 15 deletions src/genetic/model/population.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Selection } from "../methods";
import { Crossover } from "../methods/crossover";
import { Candidate } from "./candidate";
import { Gene } from "./gene";

interface PopulationOptions<T> {
select?: (candidates: Candidate<T>[]) => Candidate<T>[];
fitness?: (genes: T[]) => number;
mutate?: (genes: T[]) => T[];
cross?: (motherGenes: T[], fatherGenes: T[]) => T[][];
mutationProbability?: number;
crossProbability?: number;
ellitism?: boolean;
}

Expand All @@ -16,8 +17,8 @@ export class Population<T extends Gene> {
select: (candidates: Candidate<T>[]) => Candidate<T>[];
fitness: (genes: T[]) => number;
mutate: (genes: T[]) => T[];
mutationProbability?: number;
crossProbability?: number;
cross: (motherGenes: T[], fatherGenes: T[]) => T[][];
mutationProbability: number;
ellitism: boolean;

/**
Expand All @@ -28,13 +29,10 @@ export class Population<T extends Gene> {
*/
static generatePopulation<T>(size: number, geneGenerator: () => T[], options: PopulationOptions<T>) {
const candidates = [];
const { fitness, mutate, mutationProbability, crossProbability } = options;
const { fitness, mutate, mutationProbability } = options;
for (let i = 0; i < size; i++) {
candidates.push(new Candidate(geneGenerator(), {
fitness: fitness || (() => 0),
mutate: mutate || (g => g),
mutationProbability,
crossProbability
fitness: fitness || (() => 0)
}));
}
return new Population(candidates, options);
Expand All @@ -46,6 +44,8 @@ export class Population<T extends Gene> {
this.fitness = options.fitness || (() => 0);
this.mutate = options.mutate || (g => g);
this.ellitism = options.ellitism || false;
this.cross = options.cross || Crossover.SINGLE_POINT();
this.mutationProbability = options.mutationProbability || 0.2;
}

createNextGeneration = (): Population<T> => {
Expand All @@ -57,7 +57,7 @@ export class Population<T extends Gene> {
/**
* Cross stronger candidates and mutates their children
*/
const newCandidates = this.cross(selectedCandidates);
const newCandidates = this.crossCandidates(selectedCandidates);
/**
* Add the children to the population
*/
Expand All @@ -80,14 +80,22 @@ export class Population<T extends Gene> {
return new Population(cleanedNewGeneration, this.getOptions());
}

cross = (candidates: Candidate<T>[]): Candidate<T>[] => {
crossCandidates = (candidates: Candidate<T>[]): Candidate<T>[] => {
if (candidates.length % 2 !== 0) {
throw Error("Selection size should be a multiple of 2");
}
let children: Candidate<T>[] = [];
const children: Candidate<T>[] = [];
const options = candidates[0].getOptions();
for (let i = 0; i < candidates.length; i += 2) {
const tChildren = candidates[i].cross(candidates[i + 1]);
children = children.concat(tChildren);
let [firstChildGenes, secondChildGenes] = this.cross(candidates[i].genes, candidates[i + 1].genes);
if (Math.random() < this.mutationProbability) {
firstChildGenes = this.mutate(firstChildGenes);
}
if (Math.random() < this.mutationProbability) {
secondChildGenes = this.mutate(secondChildGenes);
}
children.push(new Candidate(firstChildGenes, options));
children.push(new Candidate(secondChildGenes, options));
}
return children;
}
Expand All @@ -97,8 +105,7 @@ export class Population<T extends Gene> {
fitness: this.fitness,
mutate: this.mutate,
select: this.select,
mutationProbability: this.mutationProbability,
crossProbability: this.crossProbability
mutationProbability: this.mutationProbability
};
}
}
75 changes: 13 additions & 62 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,13 @@
import { Selection } from "./genetic/methods";
import { Candidate, Population } from "./genetic/model";

const PASSWORD = "hardcorepasswordmasterrace";
const fitness = (password: string) => (genes: string[]) => {
const passwordArray = password.split("");
if (genes.length !== password.length) {
throw Error("passwords length not matching");
}
let score = 0;
for (let i = 0; i < genes.length; i++) {
if (genes[i] === passwordArray[i]) {
score += 1;
}
}
return score * 100 / password.length;
};

const mutate = (password: string) => (genes: string[]) => {
const genesCopy = genes.slice();
const mutatedIndex = Math.floor(Math.random() * PASSWORD.length);
genesCopy[mutatedIndex] = getRandomLetter();
return genesCopy;
};

// Create the networks
const candidates = [];

for (let i = 0; i < 100; i ++) {
const genes = generateRandomGenes();
const candidate = new Candidate<string>(
generateRandomGenes(),
{
fitness: fitness(PASSWORD),
mutate: mutate(PASSWORD),
mutationProbability: 0.4
}
);
candidates.push(candidate);
}

const initialPop = new Population(candidates);

let pop = initialPop;

for (let i = 0; i < 100; i ++) {
document.getElementById("main")!.innerHTML
+= ("<br/><br />" + pop.candidates[0].genes + ", fitness: " + pop.candidates[0].fitness(pop.candidates[0].genes));
pop = pop.createNextGeneration();
}

function generateRandomGenes() {
const genes = [];
for (let i = 0; i < PASSWORD.length; i++) {
genes.push(getRandomLetter());
}
return genes;
}

function getRandomLetter() {
return String.fromCharCode(97 + Math.floor(Math.random() * 26));
}
import { findPassword } from "./demos/findPassword";
import { salesman } from "./demos/salesman";

// findPassword("passwordhardcoretropduratrouver");
salesman([
{x: 0, y: 0},
{x: 1, y: 1},
{x: 2, y: 2},
{x: 3, y: 3},
{x: 4, y: 4},
{x: 4, y: 0},
{x: 0, y: 4}
]);

0 comments on commit 56b45be

Please sign in to comment.