Skip to content

Commit 1cd6428

Browse files
BesanHalwanorvig
authored andcommitted
Update search.py (aimacode#480)
Implemented Genetic Algorithm. Because aimacode#477 had unnecessary edits,
1 parent 4edce2a commit 1cd6428

1 file changed

Lines changed: 93 additions & 42 deletions

File tree

search.py

Lines changed: 93 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@
44
then create problem instances and solve them with calls to the various search
55
functions."""
66

7-
from utils import (
8-
is_in, argmin, argmax, argmax_random_tie, probability,
9-
weighted_sample_with_replacement, memoize, print_table, DataFile, Stack,
10-
FIFOQueue, PriorityQueue, name
11-
)
12-
from grid import distance
13-
14-
from collections import defaultdict
7+
import bisect
158
import math
169
import random
10+
import string
1711
import sys
18-
import bisect
12+
from collections import defaultdict
13+
14+
from fuzzywuzzy import fuzz
15+
16+
from grid import distance
17+
from utils import (
18+
is_in, argmin, argmax_random_tie, probability,
19+
memoize, print_table, DataFile, Stack,
20+
FIFOQueue, PriorityQueue, name
21+
)
1922

2023
infinity = float('inf')
2124

@@ -569,46 +572,94 @@ def LRTA_cost(self, s, a, s1, H):
569572
# Genetic Algorithm
570573

571574

572-
def genetic_search(problem, fitness_fn, ngen=1000, pmut=0.1, n=20):
573-
"""Call genetic_algorithm on the appropriate parts of a problem.
574-
This requires the problem to have states that can mate and mutate,
575-
plus a value method that scores states."""
576-
s = problem.initial_state
577-
states = [problem.result(s, a) for a in problem.actions(s)]
578-
random.shuffle(states)
579-
return genetic_algorithm(states[:n], problem.value, ngen, pmut)
575+
class GAState:
576+
def __init__(self, length):
577+
self.string = ''.join(random.choice(string.ascii_letters)
578+
for _ in range(length))
579+
self.fitness = -1
580580

581581

582-
def genetic_algorithm(population, fitness_fn, ngen=1000, pmut=0.1):
583-
"""[Figure 4.8]"""
584-
for i in range(ngen):
585-
new_population = []
586-
for i in range(len(population)):
587-
fitnesses = map(fitness_fn, population)
588-
p1, p2 = weighted_sample_with_replacement(2, population, fitnesses)
589-
child = p1.mate(p2)
590-
if random.uniform(0, 1) < pmut:
591-
child.mutate()
592-
new_population.append(child)
593-
population = new_population
594-
return argmax(population, key=fitness_fn)
582+
def ga(in_str=None, population=20, generations=10000):
583+
in_str_len = len(in_str)
584+
individuals = init_individual(population, in_str_len)
595585

586+
for generation in range(generations):
596587

597-
class GAState:
588+
individuals = fitness(individuals, in_str)
589+
individuals = selection(individuals)
590+
individuals = crossover(individuals, population, in_str_len)
598591

599-
"""Abstract class for individuals in a genetic search."""
592+
if any(individual.fitness >= 90 for individual in individuals):
593+
"""
594+
individuals[0] is the individual with the highest fitness,
595+
because individuals is sorted in the selection function.
596+
Thus we return the individual with the highest fitness value,
597+
among the individuals whose fitness is equal to or greater
598+
than 90.
599+
"""
600600

601-
def __init__(self, genes):
602-
self.genes = genes
601+
return individuals[0]
603602

604-
def mate(self, other):
605-
"""Return a new individual crossing self and other."""
606-
c = random.randrange(len(self.genes))
607-
return self.__class__(self.genes[:c] + other.genes[c:])
603+
individuals = mutation(individuals, in_str_len)
608604

609-
def mutate(self):
610-
"""Change a few of my genes."""
611-
raise NotImplementedError
605+
"""
606+
sufficient number of generations have passed and the individuals
607+
could not evolve to match the desired fitness value.
608+
thus we return the fittest individual among the individuals.
609+
Since individuals are sorted according to their fitness
610+
individuals[0] is the fittest.
611+
"""
612+
return individuals[0]
613+
614+
615+
def init_individual(population, length):
616+
return [GAState(length) for _ in range(population)]
617+
618+
619+
def fitness(individuals, in_str):
620+
for individual in individuals:
621+
individual.fitness = fuzz.ratio(individual.string, in_str) # noqa
622+
623+
return individuals
624+
625+
626+
def selection(individuals):
627+
individuals = sorted(
628+
individuals, key=lambda individual: individual.fitness, reverse=True)
629+
630+
individuals = individuals[:int(0.2 * len(individuals))]
631+
return individuals
632+
633+
634+
def crossover(individuals, population, in_str_len):
635+
offspring = []
636+
for _ in range(int((population - len(individuals)) / 2)):
637+
parent1 = random.choice(individuals)
638+
parent2 = random.choice(individuals)
639+
child1 = GAState(in_str_len)
640+
child2 = GAState(in_str_len)
641+
split = random.randint(0, in_str_len)
642+
child1.string = parent1.string[0:split] + parent2.string[
643+
split:in_str_len]
644+
child2.string = parent2.string[0:split] + parent1.string[
645+
split:in_str_len]
646+
offspring.append(child1)
647+
offspring.append(child2)
648+
649+
individuals.extend(offspring)
650+
return individuals
651+
652+
653+
def mutation(individuals, in_str_len):
654+
for individual in individuals:
655+
656+
for idx, param in enumerate(individual.string):
657+
if random.uniform(0.0, 1.0) <= 0.1:
658+
individual.string = individual.string[0:idx] \
659+
+ random.choice(string.ascii_letters) \
660+
+ individual.string[idx + 1:in_str_len]
661+
662+
return individuals
612663

613664
# _____________________________________________________________________________
614665
# The remainder of this file implements examples for the search algorithms.
@@ -926,7 +977,7 @@ def print_boggle(board):
926977
print()
927978

928979

929-
def boggle_neighbors(n2, cache={}):
980+
def boggle_neighbors(n2, cache={}): # noqa
930981
"""Return a list of lists, where the i-th element is the list of indexes
931982
for the neighbors of square i."""
932983
if cache.get(n2):

0 commit comments

Comments
 (0)