Krypto and 24

Daily WTF inspired me to implement a Krypto and 24 solver:

import functools
import itertools
import operator

class Node:
        def evaluate(self): raise NotImplemted()
        def print(self): raise NotImplemted()

        def __add__(self, other): return Add(self, other)
        def __sub__(self, other): return Sub(self, other)
        def __mul__(self, other): return Mul(self, other)
        def __truediv__(self, other): return Div(self, other)

        def __str__(self):
                return "<Node: {}>".format(self.print())

        def __repr__(self):
                return str(self)

class Binary(Node):
        def __init__(self, left, right):
                self.left = left
                self.right = right

        def evaluate(self):
                try:
                        return self.op(
                                self.left.evaluate(),
                                self.right.evaluate(),
                        )
                except ZeroDivisionError:
                        return float("NaN")

        def print(self):
                return "({} {} {})".format(
                                self.left.print(),
                                self.symbol(),
                                self.right.print()
                )

        def op(self, left, rigth): raise NotImplemted()
        def symbol(self): raise NotImplemted()

class Unary(Node):
        def __init__(self, op, val):
                self.op = op
                self.val = val

        def evaluate(self):
                return self.op(self.val)

        def print(self):
                return str(self.evaluate())

def BinOp(op, symbol, commutative = True):
        class inner(Binary):
                def op(self, left, right):
                        return op(left, right)

                def symbol(self):
                        return symbol

                @classmethod
                def iscommutative(cls):
                        return commutative

        return inner

Add = BinOp(operator.add, "+")
Sub = BinOp(operator.sub, "-", commutative = False)
Mul = BinOp(operator.mul, "*")
Div = BinOp(operator.truediv, "/", commutative = False)
Lit = functools.partial(Unary, lambda x: x)

OPS = [Add, Sub, Mul, Div]

def generate_expressions(nodes):
        if len(nodes) == 1:
                yield first(nodes)

        for x, y, op in itertools.product(nodes, nodes, OPS):
                isnew = not op.iscommutative() or id(x) > id(y)
                if x is not y and isnew:
                        yield generate_expressions((nodes - set([x, y])) | set([op(x, y)]))

def flatten(xs):
        for x in xs:
                try:
                        for y in flatten(x):
                                yield y
                except TypeError:
                        yield x

def first(xs):
        for x in xs:
                return x

def krypto(nums, solution):
        def issolution(expr):
                return expr.evaluate() == solution

        nodes = set(map(Lit, nums))
        expr = first(filter(issolution, flatten(generate_expressions(nodes))))
        print(expr.print(), "=", expr.evaluate())

if __name__ == "__main__":
        import sys
        args = list(map(int, sys.argv[1:]))
        krypto(args[:-1], args[-1])

My favourite part is that the core of the algorithm is 6 lines:

def generate_expressions(nodes):
        if len(nodes) == 1:
                yield first(nodes)

        for x, y, op in itertools.product(nodes, nodes, OPS):
                isnew = not op.iscommutative() or id(x) > id(y)
                if x is not y and isnew:
                        yield generate_expressions((nodes - set([x, y])) | set([op(x, y)]))

The parametric classes are also a reminder of just how badass python is:

def BinOp(op, symbol, commutative = True):
        class inner(Binary):
                def op(self, left, right):
                        return op(left, right)

                def symbol(self):
                        return symbol

                @classmethod
                def iscommutative(cls):
                        return commutative

        return inner

Edit

2010-05-16: Apparently Python comes with an implementation of cross product.