|
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