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

File tree

1 file changed

+93
-42
lines changed

1 file changed

+93
-42
lines changed

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy