Skip to content

Instantly share code, notes, and snippets.

@doctorlove
Last active July 2, 2022 04:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doctorlove/be4ebe4929855f69861c13b57dbcf3aa to your computer and use it in GitHub Desktop.
Save doctorlove/be4ebe4929855f69861c13b57dbcf3aa to your computer and use it in GitHub Desktop.
GP Fizz Buzz
# 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