99
1010import random
1111
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.
1313N_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.
1616N_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.
1919MUTATION_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.
2121random .seed (random .randint (0 , 1000 ))
2222
2323
@@ -56,20 +56,20 @@ def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int,
5656 f"{ not_in_genes_list } is not in genes list, evolution cannot converge"
5757 )
5858
59- # Generate random starting population
59+ # Generate random starting population.
6060 population = []
6161 for _ in range (N_POPULATION ):
6262 population .append ("" .join ([random .choice (genes ) for i in range (len (target ))]))
6363
64- # Just some logs to know what the algorithms is doing
64+ # Just some logs to know what the algorithms is doing.
6565 generation , total_population = 0 , 0
6666
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.
6868 while True :
6969 generation += 1
7070 total_population += len (population )
7171
72- # Random population created now it's time to evaluate
72+ # Random population created. Now it's time to evaluate.
7373 def evaluate (item : str , main_target : str = target ) -> tuple [str , float ]:
7474 """
7575 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]:
9292 # concurrent.futures.wait(futures)
9393 # population_score = [item.result() for item in futures]
9494 #
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.
9797 population_score = [evaluate (item ) for item in population ]
9898
99- # Check if there is a matching evolution
99+ # Check if there is a matching evolution.
100100 population_score = sorted (population_score , key = lambda x : x [1 ], reverse = True )
101101 if population_score [0 ][0 ] == target :
102102 return (generation , total_population , population_score [0 ][0 ])
103103
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.
106106 if debug and generation % 10 == 0 :
107107 print (
108108 f"\n Generation: { generation } "
@@ -111,21 +111,21 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]:
111111 f"\n Best string: { population_score [0 ][0 ]} "
112112 )
113113
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.
116116 population_best = population [: int (N_POPULATION / 3 )]
117117 population .clear ()
118118 population .extend (population_best )
119- # Normalize population score from 0 to 1
119+ # Normalize population score to be between 0 and 1.
120120 population_score = [
121121 (item , score / len (target )) for item , score in population_score
122122 ]
123123
124- # Select, Crossover and Mutate a new population
124+ # Select, crossover and mutate a new population.
125125 def select (parent_1 : tuple [str , float ]) -> list [str ]:
126126 """Select the second parent and generate new population"""
127127 pop = []
128- # Generate more child proportionally to the fitness score
128+ # Generate more children proportionally to the fitness score.
129129 child_n = int (parent_1 [1 ] * 100 ) + 1
130130 child_n = 10 if child_n >= 10 else child_n
131131 for _ in range (child_n ):
@@ -134,32 +134,32 @@ def select(parent_1: tuple[str, float]) -> list[str]:
134134 ][0 ]
135135
136136 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.
138138 pop .append (mutate (child_1 ))
139139 pop .append (mutate (child_2 ))
140140 return pop
141141
142142 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. """
144144 random_slice = random .randint (0 , len (parent_1 ) - 1 )
145145 child_1 = parent_1 [:random_slice ] + parent_2 [random_slice :]
146146 child_2 = parent_2 [:random_slice ] + parent_1 [random_slice :]
147147 return (child_1 , child_2 )
148148
149149 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. """
151151 child_list = list (child )
152152 if random .uniform (0 , 1 ) < MUTATION_PROBABILITY :
153153 child_list [random .randint (0 , len (child )) - 1 ] = random .choice (genes )
154154 return "" .join (child_list )
155155
156- # This is Selection
156+ # This is selection
157157 for i in range (N_SELECTED ):
158158 population .extend (select (population_score [int (i )]))
159159 # 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.
163163 if len (population ) > N_POPULATION :
164164 break
165165
0 commit comments