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
+
1
12
package main
2
13
3
14
import (
@@ -16,19 +27,26 @@ type populationItem struct {
16
27
Value float64
17
28
}
18
29
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 ) {
23
31
// 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
26
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
27
36
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 ())
29
42
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 ) {
32
50
find := func () bool {
33
51
for _ , n := range charmap {
34
52
if n == r {
@@ -43,61 +61,97 @@ func main() {
43
61
}
44
62
}
45
63
46
- // Generate random population
64
+ // Generate random starting population
47
65
pop := make ([]populationItem , populationNum , populationNum )
48
66
for i := 0 ; i < populationNum ; i ++ {
49
67
key := ""
50
- for x := 0 ; x < utf8 .RuneCountInString (sentence ); x ++ {
68
+ for x := 0 ; x < utf8 .RuneCountInString (target ); x ++ {
51
69
choice := rand .Intn (len (charmap ))
52
70
key += string (charmap [choice ])
53
71
}
54
72
pop [i ] = populationItem {key , 0 }
55
73
}
56
74
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
58
81
generatedPop += len (pop )
59
82
60
83
// Random population created now it's time to evaluate
61
84
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 ] {
65
89
pop [i ].Value ++
66
90
}
67
91
}
68
- pop [i ].Value = pop [i ].Value / float64 (len (sentenceRune ))
92
+ pop [i ].Value = pop [i ].Value / float64 (len (targetRune ))
69
93
}
70
- // Check if there is a right evolution
71
94
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 {
74
98
break
75
99
}
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 {
78
103
fmt .Println ("Generation:" , strconv .Itoa (gen ), "Analyzed:" , generatedPop , "Best:" , pop [0 ])
79
104
}
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
81
108
var popChildren []populationItem
109
+ popChildren = append (popChildren , pop [0 :int (selectionNum / 3 )]... )
110
+
111
+ // This is Selection
82
112
for i := 0 ; i < int (selectionNum ); i ++ {
83
113
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
91
118
}
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 })
93
136
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
+ }
98
144
}
99
- popChildren = append (popChildren , populationItem {string (child ), 0 })
100
145
}
101
146
pop = popChildren
102
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 := genetic_string (target , charmap )
156
+ fmt .Println ("Generation:" , strconv .Itoa (gen ), "Analyzed:" , generatedPop , "Best:" , best )
103
157
}
0 commit comments