Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 47 additions & 18 deletions evolve_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from deap import algorithms
from deap import base
from deap import tools
from functools import lru_cache


# -----------------------------------------------------------------------------
Expand All @@ -31,7 +32,6 @@
# Control whether all Messages are printed as they are evaluated
VERBOSE = True


# -----------------------------------------------------------------------------
# Message object to use in evolutionary algorithm
# -----------------------------------------------------------------------------
Expand All @@ -43,7 +43,6 @@ class FitnessMinimizeSingle(base.Fitness):
"""
weights = (-1.0, )


class Message(list):
"""
Representation of an individual Message within the population to be evolved
Expand Down Expand Up @@ -87,13 +86,27 @@ def get_text(self):
"""Return Message as string (rather than actual list of characters)"""
return "".join(self)


# -----------------------------------------------------------------------------
# Genetic operators
# -----------------------------------------------------------------------------
@lru_cache(maxsize=None)
def levenshtein_distance(a, b):
lenA = len(a)
lenB = len(b)

if lenA == 0:
return lenB
if lenB == 0:
return lenA

# TODO: Implement levenshtein_distance function (see Day 9 in-class exercises)
# HINT: Now would be a great time to implement memoization if you haven't
if a[0] == b[0]:
return levenshtein_distance(a[1:], b[1:])
l1 = levenshtein_distance(a, b[1:])
l2 = levenshtein_distance(a[1:], b)
l3 = levenshtein_distance(a[1:], b[1:])
dist = 1 + min(l1, l2, l3)

return dist

def evaluate_text(message, goal_text, verbose=VERBOSE):
"""
Expand All @@ -106,7 +119,6 @@ def evaluate_text(message, goal_text, verbose=VERBOSE):
print("{msg!s}\t[Distance: {dst!s}]".format(msg=message, dst=distance))
return (distance, ) # Length 1 tuple, required by DEAP


def mutate_text(message, prob_ins=0.05, prob_del=0.05, prob_sub=0.05):
"""
Given a Message and independent probabilities for each mutation type,
Expand All @@ -119,22 +131,40 @@ def mutate_text(message, prob_ins=0.05, prob_del=0.05, prob_sub=0.05):
Substitution: Replace one character of the Message with a random
(legal) character
"""

if random.random() < prob_ins:
# TODO: Implement insertion-type mutation
pass

# TODO: Also implement deletion and substitution mutations
# HINT: Message objects inherit from list, so they also inherit
# useful list methods
# HINT: You probably want to use the VALID_CHARS global variable
randplace = int(random.random() * len(message))
randloc = int(random.random() * len(VALID_CHARS))
randchar = VALID_CHARS[randloc]
print(randchar)

r = random.random()
if r < prob_ins: # Insertion
message.insert(randplace, randchar)
elif prob_ins < r < (prob_ins + prob_del): # Deletion
del message[randplace]
elif (prob_ins + prob_del) < r < (prob_ins + prob_del + prob_sub): # Substitution
message[randplace] = randchar
else:
pass # Most of the time, do not mutate

return (message, ) # Length 1 tuple, required by DEAP


# -----------------------------------------------------------------------------
# DEAP Toolbox and Algorithm setup
# -----------------------------------------------------------------------------
def two_point_crossover(a, b):
"""
Return tuple of crossed parent strings a, b
>>> two_point_crossover("ABCDEF", "UVWXYZ")
("ABWXYF", "UVCDEZ")
"""
maxcrosslen = min(len(a), len(b))
cx1 = random.randint(1, maxcrosslen)
cx2 = random.randint(1, maxcrosslen - 1)

anew = a[:cx1] + b[cx1:cx2] + a[cx2:]
bnew = b[:cx1] + a[cx1:cx2] + b[cx2:]

return (Message("".join(anew)), Message("".join(bnew)))

def get_toolbox(text):
"""Return DEAP Toolbox configured to evolve given 'text' string"""
Expand All @@ -149,7 +179,7 @@ def get_toolbox(text):

# Genetic operators
toolbox.register("evaluate", evaluate_text, goal_text=text)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mate", two_point_crossover)
toolbox.register("mutate", mutate_text)
toolbox.register("select", tools.selTournament, tournsize=3)

Expand Down Expand Up @@ -190,7 +220,6 @@ def evolve_string(text):

return pop, log


# -----------------------------------------------------------------------------
# Run if called from the command line
# -----------------------------------------------------------------------------
Expand Down