RosettaCodeData/Task/Evolutionary-algorithm/JavaScript/evolutionary-algorithm-1.js

231 lines
7.1 KiB
JavaScript

// ------------------------------------- Cross-browser Compatibility -------------------------------------
/* Compatibility code to reduce an array
* Source: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Reduce
*/
if (!Array.prototype.reduce) {
Array.prototype.reduce = function (fun /*, initialValue */ ) {
"use strict";
if (this === void 0 || this === null) throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function") throw new TypeError();
// no value to return if no initial value and an empty array
if (len == 0 && arguments.length == 1) throw new TypeError();
var k = 0;
var accumulator;
if (arguments.length >= 2) {
accumulator = arguments[1];
} else {
do {
if (k in t) {
accumulator = t[k++];
break;
}
// if array contains no values, no initial value to return
if (++k >= len) throw new TypeError();
}
while (true);
}
while (k < len) {
if (k in t) accumulator = fun.call(undefined, accumulator, t[k], k, t);
k++;
}
return accumulator;
};
}
/* Compatibility code to map an array
* Source: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Map
*/
if (!Array.prototype.map) {
Array.prototype.map = function (fun /*, thisp */ ) {
"use strict";
if (this === void 0 || this === null) throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function") throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in t) res[i] = fun.call(thisp, t[i], i, t);
}
return res;
};
}
/* ------------------------------------- Generator -------------------------------------
* Generates a fixed length gene sequence via a gene strategy object.
* The gene strategy object must have two functions:
* - "create": returns create a new gene
* - "mutate(existingGene)": returns mutation of an existing gene
*/
function Generator(length, mutationRate, geneStrategy) {
this.size = length;
this.mutationRate = mutationRate;
this.geneStrategy = geneStrategy;
}
Generator.prototype.spawn = function () {
var genes = [],
x;
for (x = 0; x < this.size; x += 1) {
genes.push(this.geneStrategy.create());
}
return genes;
};
Generator.prototype.mutate = function (parent) {
return parent.map(function (char) {
if (Math.random() > this.mutationRate) {
return char;
}
return this.geneStrategy.mutate(char);
}, this);
};
/* ------------------------------------- Population -------------------------------------
* Helper class that holds and spawns a new population.
*/
function Population(size, generator) {
this.size = size;
this.generator = generator;
this.population = [];
// Build initial popuation;
for (var x = 0; x < this.size; x += 1) {
this.population.push(this.generator.spawn());
}
}
Population.prototype.spawn = function (parent) {
this.population = [];
for (var x = 0; x < this.size; x += 1) {
this.population.push(this.generator.mutate(parent));
}
};
/* ------------------------------------- Evolver -------------------------------------
* Attempts to converge a population based a fitness strategy object.
* The fitness strategy object must have three function
* - "score(individual)": returns a score for an individual.
* - "compare(scoreA, scoreB)": return true if scoreA is better (ie more fit) then scoreB
* - "done( score )": return true if score is acceptable (ie we have successfully converged).
*/
function Evolver(size, generator, fitness) {
this.done = false;
this.fitness = fitness;
this.population = new Population(size, generator);
}
Evolver.prototype.getFittest = function () {
return this.population.population.reduce(function (best, individual) {
var currentScore = this.fitness.score(individual);
if (best === null || this.fitness.compare(currentScore, best.score)) {
return {
score: currentScore,
individual: individual
};
} else {
return best;
}
}, null);
};
Evolver.prototype.doGeneration = function () {
this.fittest = this.getFittest();
this.done = this.fitness.done(this.fittest.score);
if (!this.done) {
this.population.spawn(this.fittest.individual);
}
};
Evolver.prototype.run = function (onCheckpoint, checkPointFrequency) {
checkPointFrequency = checkPointFrequency || 10; // Default to Checkpoints every 10 generations
var generation = 0;
while (!this.done) {
this.doGeneration();
if (generation % checkPointFrequency === 0) {
onCheckpoint(generation, this.fittest);
}
generation += 1;
}
onCheckpoint(generation, this.fittest);
return this.fittest;
};
// ------------------------------------- Exports -------------------------------------
window.Generator = Generator;
window.Evolver = Evolver;
// helper utitlity to combine elements of two arrays.
Array.prototype.zip = function (b, func) {
var result = [],
max = Math.max(this.length, b.length),
x;
for (x = 0; x < max; x += 1) {
result.push(func(this[x], b[x]));
}
return result;
};
var target = "METHINKS IT IS LIKE A WEASEL", geneStrategy, fitness, target, generator, evolver, result;
geneStrategy = {
// The allowed character set (as an array)
characterSet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ ".split(""),
/*
Pick a random character from the characterSet
*/
create: function getRandomGene() {
var randomNumber = Math.floor(Math.random() * this.characterSet.length);
return this.characterSet[randomNumber];
}
};
geneStrategy.mutate = geneStrategy.create; // Our mutation stragtegy is to simply get a random gene
fitness = {
// The target (as an array of characters)
target: target.split(""),
equal: function (geneA, geneB) {
return (geneA === geneB ? 0 : 1);
},
sum: function (runningTotal, value) {
return runningTotal + value;
},
/*
We give one point to for each corect letter
*/
score: function (genes) {
var diff = genes.zip(this.target, this.equal); // create an array of ones and zeros
return diff.reduce(this.sum, 0); // Sum the array values together.
},
compare: function (scoreA, scoreB) {
return scoreA <= scoreB; // Lower scores are better
},
done: function (score) {
return score === 0; // We have matched the target string.
}
};
generator = new Generator(target.length, 0.05, geneStrategy);
evolver = new Evolver(100, generator, fitness);
function showProgress(generation, fittest) {
document.write("Generation: " + generation + ", Best: " + fittest.individual.join("") + ", fitness:" + fittest.score + "<br>");
}
result = evolver.run(showProgress);