-
Notifications
You must be signed in to change notification settings - Fork 2
/
genetic.ts
executable file
·112 lines (88 loc) · 3.24 KB
/
genetic.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// benchmarked vs https://subprotocol.com/system/genetic-hello-world.html
// local genetic is x2 faster
import { Genetic, Select } from '../src/genetic';
const GENERATIONS = 4000;
const POPULATION = 4000;
const solution = 'Insanity is doing the same thing over and over again and expecting different results';
export async function classicGenetic(log?: boolean) {
function randomString(len: number) {
let text = '';
const charset = 'abcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < len; i++) text += charset.charAt(Math.floor(Math.random() * charset.length));
return text;
}
function replaceAt(str, index, character) {
return str.substr(0, index) + character + str.substr(index + character.length);
}
async function randomFunction() {
// create random strings that are equal in length to solution
return randomString(solution.length);
}
async function mutationFunction(entity: string) {
// chromosomal drift
const i = Math.floor(Math.random() * entity.length);
return replaceAt(
entity,
i,
String.fromCharCode(entity.charCodeAt(i) + (Math.floor(Math.random() * 2) ? 1 : -1)),
);
}
async function crossoverFunction(mother: string, father: string) {
// two-point crossover
const len = mother.length;
let ca = Math.floor(Math.random() * len);
let cb = Math.floor(Math.random() * len);
if (ca > cb) {
const tmp = cb;
cb = ca;
ca = tmp;
}
const son = father.substr(0, ca) + mother.substr(ca, cb - ca) + father.substr(cb);
const daughter = mother.substr(0, ca) + father.substr(ca, cb - ca) + mother.substr(cb);
return [son, daughter];
}
async function fitnessFunction(entity: string) {
let fitness = 0;
for (let i = 0; i < entity.length; ++i) {
// increase fitness for each character that matches
if (entity[i] == solution[i]) fitness += 1;
// award fractions of a point as we get warmer
fitness += (127 - Math.abs(entity.charCodeAt(i) - solution.charCodeAt(i))) / 50;
}
return { fitness };
}
const population: Promise<string>[] = [];
for (let i = 0; i < POPULATION; i++) {
population.push(randomFunction());
}
const genetic = new Genetic<string>({
mutationFunction,
crossoverFunction,
fitnessFunction,
randomFunction,
populationSize: POPULATION,
fittestNSurvives: 1,
select1: Select.FittestLinear,
select2: Select.Tournament3,
mutateProbablity: 0.8,
crossoverProbablity: 0.8,
});
async function solve() {
await genetic.seed();
for (let i = 0; i <= GENERATIONS; i++) {
if (log) {
console.count('gen');
}
await genetic.estimate();
const bestOne = genetic.best()[0];
if (log) {
console.log(`${bestOne.entity} - ${bestOne.fitness}`);
}
await genetic.breed();
if (bestOne.entity === solution) {
return i;
}
}
}
return solve();
}