Skip to content

Commit e5b0b60

Browse files
Merge pull request #157 from D4rkia/feat/genetic_algorithm
Add a missing "genetic algorithm" folder with a basic algorithm inside
2 parents 100e8df + 2729e3b commit e5b0b60

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

genetic-algorithm/genetic_algo.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
Simple multithreaded algorithm to show how the 4 phases of a genetic
3+
algorithm works (Evaluation, Selection, Crossover and Mutation)
4+
https://en.wikipedia.org/wiki/Genetic_algorithm
5+
6+
Link to the same algorithm implemented in python:
7+
https://github.com/TheAlgorithms/Python/blob/master/genetic_algorithm/basic_string.py
8+
9+
Author: D4rkia
10+
*/
11+
12+
package main
13+
14+
import (
15+
"errors"
16+
"fmt"
17+
"math/rand"
18+
"os"
19+
"sort"
20+
"strconv"
21+
"time"
22+
"unicode/utf8"
23+
)
24+
25+
type populationItem struct {
26+
Key string
27+
Value float64
28+
}
29+
30+
func geneticString(target string, charmap []rune) (int, int, string) {
31+
// Define parameters
32+
// Maximum size of the population. bigger could be faster but is more memory expensive
33+
populationNum := 200
34+
// Number of elements selected in every generation for evolution the selection takes
35+
// place from the best to the worst of that generation must be smaller than N_POPULATION
36+
selectionNum := 50
37+
// Probability that an element of a generation can mutate changing one of its genes this
38+
// guarantees that all genes will be used during evolution
39+
mutationProb := .4
40+
// Just a seed to improve randomness required by the algorithm
41+
rand.Seed(time.Now().UnixNano())
42+
43+
// Verify if 'populationNum' s bigger than 'selectionNum'
44+
if populationNum < selectionNum {
45+
fmt.Println(errors.New("PopulationNum must be bigger tha selectionNum "))
46+
os.Exit(1)
47+
}
48+
// Verify that the target contains no genes besides the ones inside genes variable.
49+
for position, r := range []rune(target) {
50+
find := func() bool {
51+
for _, n := range charmap {
52+
if n == r {
53+
return true
54+
}
55+
}
56+
return false
57+
}
58+
if !find() {
59+
fmt.Println(errors.New("Character not aviable in charmap"), position, "\"", string(r), "\"")
60+
os.Exit(1)
61+
}
62+
}
63+
64+
// Generate random starting population
65+
pop := make([]populationItem, populationNum, populationNum)
66+
for i := 0; i < populationNum; i++ {
67+
key := ""
68+
for x := 0; x < utf8.RuneCountInString(target); x++ {
69+
choice := rand.Intn(len(charmap))
70+
key += string(charmap[choice])
71+
}
72+
pop[i] = populationItem{key, 0}
73+
}
74+
75+
// Just some logs to know what the algorithms is doing
76+
gen, generatedPop := 0, 0
77+
78+
// This loop will end when we will find a perfect match for our target
79+
for {
80+
gen++
81+
generatedPop += len(pop)
82+
83+
// Random population created now it's time to evaluate
84+
for i, item := range pop {
85+
pop[i].Value = 0
86+
itemKey, targetRune := []rune(item.Key), []rune(target)
87+
for x := 0; x < len(target); x++ {
88+
if itemKey[x] == targetRune[x] {
89+
pop[i].Value++
90+
}
91+
}
92+
pop[i].Value = pop[i].Value / float64(len(targetRune))
93+
}
94+
sort.SliceStable(pop, func(i, j int) bool { return pop[i].Value > pop[j].Value })
95+
96+
// Check if there is a matching evolution
97+
if pop[0].Key == target {
98+
break
99+
}
100+
// Print the best resultPrint the Best result every 10 generations
101+
// just to know that the algorithm is working
102+
if gen%10 == 0 {
103+
fmt.Println("Generation:", strconv.Itoa(gen), "Analyzed:", generatedPop, "Best:", pop[0])
104+
}
105+
106+
// Generate a new population vector keeping some of the best evolutions
107+
// Keeping this avoid regression of evolution
108+
var popChildren []populationItem
109+
popChildren = append(popChildren, pop[0:int(selectionNum/3)]...)
110+
111+
// This is Selection
112+
for i := 0; i < int(selectionNum); i++ {
113+
parent1 := pop[i]
114+
// Generate more child proportionally to the fitness score
115+
nChild := (parent1.Value * 100) + 1
116+
if nChild >= 10 {
117+
nChild = 10
118+
}
119+
for x := 0.0; x < nChild; x++ {
120+
parent2 := pop[rand.Intn(selectionNum)]
121+
// Crossover
122+
split := rand.Intn(utf8.RuneCountInString(target))
123+
child1 := append([]rune(parent1.Key)[:split], []rune(parent2.Key)[split:]...)
124+
child2 := append([]rune(parent2.Key)[:split], []rune(parent1.Key)[split:]...)
125+
//Clean fitness value
126+
// Mutate
127+
if rand.Float64() < mutationProb {
128+
child1[rand.Intn(len(child1))] = charmap[rand.Intn(len(charmap))]
129+
}
130+
if rand.Float64() < mutationProb {
131+
child2[rand.Intn(len(child2))] = charmap[rand.Intn(len(charmap))]
132+
}
133+
// Push into 'popChildren'
134+
popChildren = append(popChildren, populationItem{string(child1), 0})
135+
popChildren = append(popChildren, populationItem{string(child2), 0})
136+
137+
// Check if the population has already reached the maximum value and if so,
138+
// break the cycle. If this check is disabled the algorithm will take
139+
// forever to compute large strings but will also calculate small string in
140+
// a lot fewer generationsù
141+
if len(popChildren) >= selectionNum {
142+
break
143+
}
144+
}
145+
}
146+
pop = popChildren
147+
}
148+
return gen, generatedPop, pop[0].Key
149+
}
150+
151+
func main() {
152+
// Define parameters
153+
target := string("This is a genetic algorithm to evaluate, combine, evolve and mutate a string!")
154+
charmap := []rune(" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,;!?+-*#@^'èéòà€ù=)(&%$£/\\")
155+
gen, generatedPop, best := geneticString(target, charmap)
156+
fmt.Println("Generation:", strconv.Itoa(gen), "Analyzed:", generatedPop, "Best:", best)
157+
}

0 commit comments

Comments
 (0)