|
4 | 4 | then create problem instances and solve them with calls to the various search |
5 | 5 | functions.""" |
6 | 6 |
|
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 |
15 | 8 | import math |
16 | 9 | import random |
| 10 | +import string |
17 | 11 | 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 | +) |
19 | 22 |
|
20 | 23 | infinity = float('inf') |
21 | 24 |
|
@@ -569,46 +572,94 @@ def LRTA_cost(self, s, a, s1, H): |
569 | 572 | # Genetic Algorithm |
570 | 573 |
|
571 | 574 |
|
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 |
580 | 580 |
|
581 | 581 |
|
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) |
595 | 585 |
|
| 586 | + for generation in range(generations): |
596 | 587 |
|
597 | | -class GAState: |
| 588 | + individuals = fitness(individuals, in_str) |
| 589 | + individuals = selection(individuals) |
| 590 | + individuals = crossover(individuals, population, in_str_len) |
598 | 591 |
|
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 | + """ |
600 | 600 |
|
601 | | - def __init__(self, genes): |
602 | | - self.genes = genes |
| 601 | + return individuals[0] |
603 | 602 |
|
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) |
608 | 604 |
|
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 |
612 | 663 |
|
613 | 664 | # _____________________________________________________________________________ |
614 | 665 | # The remainder of this file implements examples for the search algorithms. |
@@ -926,7 +977,7 @@ def print_boggle(board): |
926 | 977 | print() |
927 | 978 |
|
928 | 979 |
|
929 | | -def boggle_neighbors(n2, cache={}): |
| 980 | +def boggle_neighbors(n2, cache={}): # noqa |
930 | 981 | """Return a list of lists, where the i-th element is the list of indexes |
931 | 982 | for the neighbors of square i.""" |
932 | 983 | if cache.get(n2): |
|
0 commit comments