-
-
Save doctorlove/be4ebe4929855f69861c13b57dbcf3aa to your computer and use it in GitHub Desktop.
GP Fizz Buzz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# based on Symbolic Regression part of EAP. | |
# | |
# EAP is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU Lesser General Public License as | |
# published by the Free Software Foundation, either version 3 of | |
# the License, or (at your option) any later version. | |
# | |
# EAP is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU Lesser General Public License for more details. | |
# | |
# You should have received a copy of the GNU Lesser General Public | |
# License along with EAP. If not, see <http://www.gnu.org/licenses/>. | |
import argparse | |
import operator | |
import math | |
import random | |
import numpy | |
from deap import algorithms | |
from deap import base | |
from deap import creator | |
from deap import tools | |
from deap import gp | |
# Define new functions | |
def protectedDiv(left, right): | |
try: | |
return left / right | |
except ZeroDivisionError: | |
return 1 | |
def Fizz(x): | |
return "Fizz" | |
def Buzz(x): | |
return "Buzz" | |
def if_then_else(x,y,z): | |
if x: | |
return y | |
else: | |
return z | |
def mod3(x): | |
return operator.mod(x, 3) == 0 | |
def mod5(x): | |
return operator.mod(x, 5) == 0 | |
def mod15(x): | |
return operator.mod(x, 15) == 0 | |
def both(x, y): | |
return x and y | |
def either(x, y): | |
return x or y | |
primitive_set = gp.PrimitiveSet("MAIN", 1) | |
#primitive_set.addPrimitive(operator.eq, 2) | |
#primitive_set.addPrimitive(operator.ne, 2) | |
primitive_set.addPrimitive(operator.add, 2) | |
primitive_set.addPrimitive(operator.sub, 2) | |
primitive_set.addPrimitive(operator.mul, 2) | |
primitive_set.addPrimitive(operator.and_, 2) #bitwise? | |
primitive_set.addPrimitive(operator.or_, 2) #bitwise? | |
primitive_set.addPrimitive(both, 2) | |
primitive_set.addPrimitive(either, 2) | |
primitive_set.addPrimitive(operator.mod, 2) | |
primitive_set.addPrimitive(str, 1) | |
primitive_set.addPrimitive(if_then_else, 3) | |
primitive_set.addPrimitive(mod3, 1) # Does it really need this | |
primitive_set.addPrimitive(mod5, 1) #or this? Seems so - why though? It manages fizz only with this, but doesn't use mod | |
#maybe needed primitive_set.addTerminal(3) and primitive_set.addTerminal(5) to be able to do this | |
primitive_set.addPrimitive(mod15, 1) ##why do I have to do this? # Did this get worse - got 93% at some point | |
#primitive_set.addTerminal(3) | |
#primitive_set.addTerminal(5) | |
primitive_set.addTerminal("Buzz") | |
primitive_set.addTerminal("Fizz") | |
primitive_set.addTerminal("FizzBuzz") ##why do I have to do this? | |
#TODO - can I get it to find the numbers? | |
#primitive_set.addEphemeralConstant("rand101", lambda: random.randint(-1,1)) | |
#primitive_set.addEphemeralConstant("randints", lambda: random.randint(0,10)) | |
primitive_set.renameArguments(ARG0='x') | |
#do I need ifs and ors ? | |
creator.create("FitnessMax", base.Fitness, weights=(1.0,)) | |
creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMax) | |
toolbox = base.Toolbox() | |
toolbox.register("expr", gp.genHalfAndHalf, pset=primitive_set, min_=1, max_=2) #max height was 2; 5 got to 94%, then started getting worse | |
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.expr) | |
toolbox.register("population", tools.initRepeat, list, toolbox.individual) | |
toolbox.register("compile", gp.compile, pset=primitive_set) | |
def every_result_is_Fizz_Buzz_FizzBuzz_or_a_decimal(results): | |
return len([x for x in results if (type(x)!= int and x not in ['Fizz', 'Buzz', 'FizzBuzz'])])==0 | |
def every_decimal_result_corresponds_to_its_ordinal_position(results): | |
return all([i==x for i, x in enumerate(results) if type(x) == int]) | |
def every_third_result_contains_Fizz(results): | |
return all(["Fizz" in str(z) for z in results[::3]]) | |
def every_fifth_result_contains_Buzz(results): | |
return all(["Buzz" in str(z) for z in results[::5]]) | |
def every_fifteenth_result_contains_FizzBuzz(results): | |
return all(["FizzBuzz" in str(z) for z in results[::15]]) | |
def the_ordinal_position_of_every_Fizz_result_is_divisible_by_3(results): | |
return all([i%3==0 for i, x in enumerate(results) if 'Fizz' in str(x)]) | |
def the_ordinal_position_of_every_Buzz_result_is_divisible_by_5(results): | |
return all([i%5==0 for i, x in enumerate(results) if 'Buzz' in str(x)]) | |
def the_ordinal_position_of_every_FizzBuzz_result_is_divisible_by_15(results): | |
return all([i%15==0 for i, x in enumerate(results) if 'FizzBuzz' in str(x)]) | |
def fizz_buzz(func, points): | |
passed = 0 | |
def safe_run(func, x): | |
try: | |
return func(x) | |
except: | |
return -1 | |
#results = [safe_run(func, x) for x in points] | |
results = [safe_run(func, x) for x in range(101)] | |
if every_result_is_Fizz_Buzz_FizzBuzz_or_a_decimal(results): | |
passed += 1 | |
if every_decimal_result_corresponds_to_its_ordinal_position(results): | |
passed += 1 | |
if every_third_result_contains_Fizz(results): | |
passed += 1 | |
if every_fifth_result_contains_Buzz(results): | |
passed += 1 | |
if every_fifteenth_result_contains_FizzBuzz(results): | |
passed += 1 | |
if the_ordinal_position_of_every_Fizz_result_is_divisible_by_3(results): | |
passed += 1 | |
if the_ordinal_position_of_every_Buzz_result_is_divisible_by_5(results): | |
passed += 1 | |
if the_ordinal_position_of_every_FizzBuzz_result_is_divisible_by_15(results): | |
passed += 1 | |
return passed | |
def fizz_only(func, points): | |
passed = 0 | |
for x in points: | |
try: | |
output = func(x) | |
except: | |
#might get ZeroDivisionError: integer division or modulo by zero | |
#print(func) | |
output = -1 | |
#uh-oh | |
if operator.mod(x, 3) == 0 and output == 'Fizz': | |
passed += 1 | |
elif operator.mod(x, 3) != 0 and output == x: #forgot modulus, and got scores of 100 | |
passed += 1 | |
return passed | |
def odd_even(func, points): | |
passed = 0 | |
for x in points: | |
try: | |
output = func(x) | |
except: | |
#might get ZeroDivisionError: integer division or modulo by zero | |
#print(func) | |
output = -1 | |
#uh-oh | |
if operator.mod(x, 2) == 0 and output == 'Buzz': | |
passed += 1 | |
if operator.mod(x, 2) == 1 and output == 'Fizz': | |
passed += 1 | |
return passed | |
def register(fn): | |
def fitness(individual, points): # This is our custom evaluation function | |
# Transform the tree expression in a callable function | |
func = toolbox.compile(expr=individual) | |
#print(func) #just showing a lambda | |
# Evaluate the result, somehow | |
# return tests_passed(func, points), | |
return fn(func, points), | |
toolbox.register("evaluate", fitness, points=range(101)) # and we register it here | |
toolbox.register("select", tools.selTournament, tournsize=3)#tried 2, got 80 for fizz buzz, then 3; back to 2, which collapses to Buzz without the mo3 and mod5 ops | |
toolbox.register("mate", gp.cxOnePoint) | |
toolbox.register("expr_mut", gp.genFull, min_=0, max_=2) | |
toolbox.register("mutate", gp.mutUniform, expr=toolbox.expr_mut, pset=primitive_set) | |
toolbox.decorate("mate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17)) | |
toolbox.decorate("mutate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17)) | |
def main(fn): | |
random.seed(318) | |
register(fn) | |
pop = toolbox.population(n=4000)#5000 seemed ok, 40 and 400 nowhere near enough, 4000 ok with mod3 and mod5 | |
hof = tools.HallOfFame(1) | |
stats_fit = tools.Statistics(lambda ind: ind.fitness.values) | |
stats_size = tools.Statistics(len) | |
mstats = tools.MultiStatistics(fitness=stats_fit, size=stats_size) | |
mstats.register("avg", numpy.mean) | |
mstats.register("std", numpy.std) | |
mstats.register("min", numpy.min) | |
mstats.register("max", numpy.max) | |
pCrossover = 0.75 | |
pMutation = 0.5 | |
nGen = 75 | |
pop, log = algorithms.eaSimple(pop, toolbox, pCrossover, pMutation, nGen, stats=mstats, | |
halloffame=hof, verbose=True) #did go to 75 to begin with; tried 150 | |
return pop, log, hof | |
if __name__ == "__main__": | |
functions = { | |
"odd_even": odd_even, | |
"fizz_buzz": fizz_buzz, | |
"fizz_only": fizz_only | |
} | |
parser = argparse.ArgumentParser() | |
parser.add_argument('function', help="One of: " + ",".join(functions.keys())) | |
args = parser.parse_args() | |
pop, log, hof = main(functions[args.function]) | |
#toolbox.evaluate(hof[0]) # to see what the fitness score is | |
fn = toolbox.compile(expr=hof[0]) | |
def safe_call(winner, x): | |
try: | |
return winner(x) | |
except: | |
return "?" | |
print([safe_call(fn, x) for x in range(101)]) | |
nodes, edges, labels = gp.graph(hof[0]) | |
print(nodes) | |
print(edges) | |
print(labels) | |
score = functions[args.function](fn, range(100)) | |
print(score) | |
print(str(hof[0])) | |
import pygraphviz as pgv | |
g = pgv.AGraph() | |
g.add_nodes_from(nodes) | |
g.add_edges_from(edges) | |
g.layout(prog="dot") | |
for i in nodes: | |
n = g.get_node(i) | |
n.attr["label"] = labels[i] | |
g.draw("tree.pdf") | |
g.to_string() | |
g.write() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment