9
9
10
10
import random
11
11
12
- # Maximum size of the population. bigger could be faster but is more memory expensive
12
+ # Maximum size of the population. Bigger could be faster but is more memory expensive.
13
13
N_POPULATION = 200
14
- # Number of elements selected in every generation for evolution the selection takes
15
- # place from the best to the worst of that generation must be smaller than N_POPULATION
14
+ # Number of elements selected in every generation of evolution. The selection takes
15
+ # place from best to worst of that generation and must be smaller than N_POPULATION.
16
16
N_SELECTED = 50
17
- # Probability that an element of a generation can mutate changing one of its genes this
18
- # guarantees that all genes will be used during evolution
17
+ # Probability that an element of a generation can mutate, changing one of its genes.
18
+ # This will guarantee that all genes will be used during evolution.
19
19
MUTATION_PROBABILITY = 0.4
20
- # just a seed to improve randomness required by the algorithm
20
+ # Just a seed to improve randomness required by the algorithm.
21
21
random .seed (random .randint (0 , 1000 ))
22
22
23
23
@@ -56,20 +56,20 @@ def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int,
56
56
f"{ not_in_genes_list } is not in genes list, evolution cannot converge"
57
57
)
58
58
59
- # Generate random starting population
59
+ # Generate random starting population.
60
60
population = []
61
61
for _ in range (N_POPULATION ):
62
62
population .append ("" .join ([random .choice (genes ) for i in range (len (target ))]))
63
63
64
- # Just some logs to know what the algorithms is doing
64
+ # Just some logs to know what the algorithms is doing.
65
65
generation , total_population = 0 , 0
66
66
67
- # This loop will end when we will find a perfect match for our target
67
+ # This loop will end when we find a perfect match for our target.
68
68
while True :
69
69
generation += 1
70
70
total_population += len (population )
71
71
72
- # Random population created now it's time to evaluate
72
+ # Random population created. Now it's time to evaluate.
73
73
def evaluate (item : str , main_target : str = target ) -> tuple [str , float ]:
74
74
"""
75
75
Evaluate how similar the item is with the target by just
@@ -92,17 +92,17 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]:
92
92
# concurrent.futures.wait(futures)
93
93
# population_score = [item.result() for item in futures]
94
94
#
95
- # but with a simple algorithm like this will probably be slower
96
- # we just need to call evaluate for every item inside population
95
+ # but with a simple algorithm like this, it will probably be slower.
96
+ # We just need to call evaluate for every item inside the population.
97
97
population_score = [evaluate (item ) for item in population ]
98
98
99
- # Check if there is a matching evolution
99
+ # Check if there is a matching evolution.
100
100
population_score = sorted (population_score , key = lambda x : x [1 ], reverse = True )
101
101
if population_score [0 ][0 ] == target :
102
102
return (generation , total_population , population_score [0 ][0 ])
103
103
104
- # Print the Best result every 10 generation
105
- # just to know that the algorithm is working
104
+ # Print the best result every 10 generation.
105
+ # Just to know that the algorithm is working.
106
106
if debug and generation % 10 == 0 :
107
107
print (
108
108
f"\n Generation: { generation } "
@@ -111,21 +111,21 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]:
111
111
f"\n Best string: { population_score [0 ][0 ]} "
112
112
)
113
113
114
- # Flush the old population keeping some of the best evolutions
115
- # Keeping this avoid regression of evolution
114
+ # Flush the old population, keeping some of the best evolutions.
115
+ # Keeping this avoid regression of evolution.
116
116
population_best = population [: int (N_POPULATION / 3 )]
117
117
population .clear ()
118
118
population .extend (population_best )
119
- # Normalize population score from 0 to 1
119
+ # Normalize population score to be between 0 and 1.
120
120
population_score = [
121
121
(item , score / len (target )) for item , score in population_score
122
122
]
123
123
124
- # Select, Crossover and Mutate a new population
124
+ # Select, crossover and mutate a new population.
125
125
def select (parent_1 : tuple [str , float ]) -> list [str ]:
126
126
"""Select the second parent and generate new population"""
127
127
pop = []
128
- # Generate more child proportionally to the fitness score
128
+ # Generate more children proportionally to the fitness score.
129
129
child_n = int (parent_1 [1 ] * 100 ) + 1
130
130
child_n = 10 if child_n >= 10 else child_n
131
131
for _ in range (child_n ):
@@ -134,32 +134,32 @@ def select(parent_1: tuple[str, float]) -> list[str]:
134
134
][0 ]
135
135
136
136
child_1 , child_2 = crossover (parent_1 [0 ], parent_2 )
137
- # Append new string to the population list
137
+ # Append new string to the population list.
138
138
pop .append (mutate (child_1 ))
139
139
pop .append (mutate (child_2 ))
140
140
return pop
141
141
142
142
def crossover (parent_1 : str , parent_2 : str ) -> tuple [str , str ]:
143
- """Slice and combine two string in a random point"""
143
+ """Slice and combine two string at a random point. """
144
144
random_slice = random .randint (0 , len (parent_1 ) - 1 )
145
145
child_1 = parent_1 [:random_slice ] + parent_2 [random_slice :]
146
146
child_2 = parent_2 [:random_slice ] + parent_1 [random_slice :]
147
147
return (child_1 , child_2 )
148
148
149
149
def mutate (child : str ) -> str :
150
- """Mutate a random gene of a child with another one from the list"""
150
+ """Mutate a random gene of a child with another one from the list. """
151
151
child_list = list (child )
152
152
if random .uniform (0 , 1 ) < MUTATION_PROBABILITY :
153
153
child_list [random .randint (0 , len (child )) - 1 ] = random .choice (genes )
154
154
return "" .join (child_list )
155
155
156
- # This is Selection
156
+ # This is selection
157
157
for i in range (N_SELECTED ):
158
158
population .extend (select (population_score [int (i )]))
159
159
# Check if the population has already reached the maximum value and if so,
160
- # break the cycle. if this check is disabled the algorithm will take
161
- # forever to compute large strings but will also calculate small string in
162
- # a lot fewer generations
160
+ # break the cycle. If this check is disabled, the algorithm will take
161
+ # forever to compute large strings, but will also calculate small strings in
162
+ # a far fewer generations.
163
163
if len (population ) > N_POPULATION :
164
164
break
165
165
0 commit comments