Skip to content

Commit af323c9

Browse files
authored
Improve comments
1 parent bba6aba commit af323c9

File tree

1 file changed

+89
-35
lines changed

1 file changed

+89
-35
lines changed

genetic-algorithm/genetic_algo.go

Lines changed: 89 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
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+
112
package main
213

314
import (
@@ -16,19 +27,26 @@ type populationItem struct {
1627
Value float64
1728
}
1829

19-
func main() {
20-
// Define a random seed
21-
rand.Seed(time.Now().UnixNano())
22-
30+
func genetic_string(target string, charmap []rune) (int, int, string) {
2331
// Define parameters
24-
sentence := string("This is a genetic algorithm to evaluate, combine, evolve mutate a string!")
25-
charmap := []rune(" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,;!?+-*#@^'èéòà€ù=)(&%$£/\\")
32+
// Maximum size of the population. bigger could be faster but is more memory expensive
2633
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
2736
selectionNum := 50
28-
mutationProb := .1
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())
2942

30-
// Verify the presence of all char in sentence
31-
for position, r := range []rune(sentence) {
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) {
3250
find := func() bool {
3351
for _, n := range charmap {
3452
if n == r {
@@ -43,61 +61,97 @@ func main() {
4361
}
4462
}
4563

46-
// Generate random population
64+
// Generate random starting population
4765
pop := make([]populationItem, populationNum, populationNum)
4866
for i := 0; i < populationNum; i++ {
4967
key := ""
50-
for x := 0; x < utf8.RuneCountInString(sentence); x++ {
68+
for x := 0; x < utf8.RuneCountInString(target); x++ {
5169
choice := rand.Intn(len(charmap))
5270
key += string(charmap[choice])
5371
}
5472
pop[i] = populationItem{key, 0}
5573
}
5674

57-
for gen, generatedPop := 1, 0; ; gen++ {
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 += 1
5881
generatedPop += len(pop)
5982

6083
// Random population created now it's time to evaluate
6184
for i, item := range pop {
62-
itemKey, sentenceRune := []rune(item.Key), []rune(sentence)
63-
for x := 0; x < len(sentence); x++ {
64-
if itemKey[x] == sentenceRune[x] {
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] {
6589
pop[i].Value++
6690
}
6791
}
68-
pop[i].Value = pop[i].Value / float64(len(sentenceRune))
92+
pop[i].Value = pop[i].Value / float64(len(targetRune))
6993
}
70-
// Check if there is a right evolution
7194
sort.SliceStable(pop, func(i, j int) bool { return pop[i].Value > pop[j].Value })
72-
if pop[0].Key == sentence {
73-
fmt.Println("Generation:", strconv.Itoa(gen), "Analyzed:", generatedPop, "Best:", pop[0])
95+
96+
// Check if there is a matching evolution
97+
if pop[0].Key == target {
7498
break
7599
}
76-
// Print the best result
77-
if gen%1000 == 0 {
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 {
78103
fmt.Println("Generation:", strconv.Itoa(gen), "Analyzed:", generatedPop, "Best:", pop[0])
79104
}
80-
// Combine, Evolve and Mutate
105+
106+
// Generate a new population vector keeping some of the best evolutions
107+
// Keeping this avoid regression of evolution
81108
var popChildren []populationItem
109+
popChildren = append(popChildren, pop[0:int(selectionNum/3)]...)
110+
111+
// This is Selection
82112
for i := 0; i < int(selectionNum); i++ {
83113
parent1 := pop[i]
84-
parent2 := pop[i+1]
85-
split := rand.Intn(utf8.RuneCountInString(sentence))
86-
87-
// Save Children 1
88-
child := append([]rune(parent1.Key)[:split], []rune(parent2.Key)[split:]...)
89-
if rand.Float64() > mutationProb {
90-
child[rand.Intn(len(child))] = charmap[rand.Intn(len(charmap))]
114+
// Generate more child proportionally to the fitness score
115+
child_n := (parent1.Value * 100) + 1
116+
if child_n >= 10 {
117+
child_n = 10
91118
}
92-
popChildren = append(popChildren, populationItem{string(child), 0})
119+
for x := 0.0; x < child_n; 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})
93136

94-
// Save Children 2
95-
child = append([]rune(parent2.Key)[:split], []rune(parent1.Key)[split:]...)
96-
if rand.Float64() > mutationProb {
97-
child[rand.Intn(len(child))] = charmap[rand.Intn(len(charmap))]
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+
}
98144
}
99-
popChildren = append(popChildren, populationItem{string(child), 0})
100145
}
101146
pop = popChildren
102147
}
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 := genetic_string(target, charmap)
156+
fmt.Println("Generation:", strconv.Itoa(gen), "Analyzed:", generatedPop, "Best:", best)
103157
}

0 commit comments

Comments
 (0)