// ------------------------------------- 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 + "
"); } result = evolver.run(showProgress);