Created
April 10, 2021 22:56
-
-
Save davidsaccavino/46f6d2e7d24c75c200e2de82fccd23b7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function random(min, max) { | |
min = Math.ceil(min); | |
max = Math.floor(max); | |
// The maximum is exclusive and the minimum is inclusive | |
return Math.floor(Math.random() * (max - min)) + min; | |
} | |
function generateLetter() { | |
const code = random(97, 123); // ASCII char codes | |
return String.fromCharCode(code); | |
} | |
class Member { | |
constructor(goal){ | |
this.goal = goal; | |
this.keys = []; | |
for(let i = 0; i < goal.length; i+=1){ | |
this.keys[i] = generateLetter(); | |
} | |
} | |
fitness(){ | |
let match = 0; | |
// finds the number of matches in comparison to the goal | |
for(let i = 0; i < this.keys.length; i += 1){ | |
if(this.keys[i] == this.goal[i]){ | |
match += 1; | |
} | |
} | |
return match / this.goal.length; | |
} | |
crossover(partner){ | |
const { length } = this.goal; | |
const child = new Member(this.goal); | |
const midpoint = random(0, length); | |
// this randomly selects phenotypes to create a child | |
// by randomly selecting a midpoint | |
for(let i = 0; i < length; i+= 1){ | |
if(i > midpoint){ | |
child.keys[i] = this.keys[i]; | |
} | |
else | |
{ | |
child.keys[i] = partner.keys[i]; | |
} | |
} | |
return child; | |
} | |
mutate(mutationRate){ | |
for(let i = 0; i < this.keys.length; i += 1) { | |
// if below prefedine mutation rate | |
// generate a new random letter on this position | |
if(Math.random() < mutationRate){ | |
this.keys[i] = generateLetter(); | |
} | |
} | |
} | |
} | |
class Population { | |
constructor(size, goal, mutationRate){ | |
size = size || 1; | |
this.members = []; | |
this.mutationRate = mutationRate | |
for(let i=0; i < size; i+=1){ | |
this.members.push(new Member(goal)); | |
} | |
} | |
_naturalSelection() { | |
let matingPool = []; | |
this.members.forEach( (m) => { | |
// The more fit the mate, the more times it shows up in the mating pool | |
// making more fit mates more likely to pass on their genetics | |
// if fitness == 0, add just one member | |
const f = Math.floor(m.fitness() * 100 || 1); | |
// adds the mate once to the matingPool for each fitness | |
for(let i = 0; i < f; i +=1){ | |
matingPool.push(m); | |
} | |
}); | |
return matingPool; | |
} | |
_reproduce(matingPool){ | |
for(let i = 0; i < this.members.length; i+= 1){ | |
// pick 2 random members/parents from the mating pool | |
const parentA = matingPool[random(0, matingPool.length)]; | |
const parentB = matingPool[random(0, matingPool.length)]; | |
// perform crossover | |
const child = parentA.crossover(parentB); | |
// perform mutation | |
child.mutate(this.mutationRate); | |
this.members[i] = child; | |
} | |
} | |
evolve(generations){ | |
for(let i = 0; i < generations; i += 1){ | |
const pool = this._naturalSelection(); | |
this._reproduce(pool); | |
} | |
} | |
} | |
function generate(populationSize, goal, mutationRate, generations) { | |
// Create a population and evlove for N generations | |
const population = new Population(populationSize, goal, mutationRate); | |
population.evolve(generations); | |
// Get the typed words from all members, and find if someone was able to type the target | |
const membersKeys = population.members.map((m) => m.keys.join('')); | |
const perfectCandidatesNum = membersKeys.filter((w) => w === goal); | |
// Print the results | |
console.log(membersKeys); | |
console.log(`${perfectCandidatesNum ? perfectCandidatesNum.length : 0} member(s) typed "${goal}`); | |
} | |
generate(250, 'helloworld', 0.025, 50); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment