#!/usr/bin/env python # Copyright (C) 2008 Jonas Wagner # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program 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 General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # To contact the autor please write to Jonas Wagner . # Solver for the 1,3,4,6 problem and similar games import math class ExpressionTree: '''an expression: either '=' and a number or an operator with expressions as operands''' order = {'=': 1, '+': 2, '-': 3, '*': 4, '/': 5} precedence = {'=': 3, '+': 1, '-': 1, '*': 2, '/': 2} # operators and commutativity operators = {'+': True, '-': False, '*': True, '/': False} # numbers is a list of lists of the form [number, boolean] indicating an # available number and whether it's used already def __init__(self, numbers): self.expression = () self.numbers = numbers def numbersUsed(self): if self.expression == (): return [] if self.expression[0] == '=': return [self.expression[1]] else: return self.expression[1].numbersUsed() + self.expression[2].numbersUsed() def evaluate(self): try: if self.expression[0] == '=': return self.numbers[self.expression[1]][0] elif self.expression[0] == '+': return self.expression[1].evaluate() + self.expression[2].evaluate() elif self.expression[0] == '-': return self.expression[1].evaluate() - self.expression[2].evaluate() elif self.expression[0] == '*': return self.expression[1].evaluate() * self.expression[2].evaluate() elif self.expression[0] == '/': return self.expression[1].evaluate() / self.expression[2].evaluate() except (ZeroDivisionError, TypeError): return "Undefined (division by zero)" def multiplicity(self): if self.expression[0] == '=' : return 1 if self.expression[0] in ('-', '/') : return self.expression[1].multiplicity() * self.expression[2].multiplicity() else: # '*' or '+' return 2 * self.expression[1].multiplicity() * self.expression[2].multiplicity() def lessThan(self, other): selfOrder = ExpressionTree.order[self.expression[0]] otherOrder = ExpressionTree.order[other.expression[0]] if selfOrder < otherOrder: return True elif selfOrder > otherOrder: return False else: if self.expression[0] == '=': return self.expression[1] < other.expression[1] # two numbers else: # two operators if self.expression[1].lessThan(other.expression[1]): return True elif other.expression[1].lessThan(self.expression[1]): return False else: return self.expression[2].lessThan(other.expression[2]) def __str__(self): if not self.expression: return "" if (self.expression[0] == '='): return str(self.numbers[self.expression[1]][0]) res = [] if self.precedence[self.expression[0]] > self.precedence[self.expression[1].expression[0]]: res.append('(' + self.expression[1].__str__() + ')') else: res.append(self.expression[1].__str__()) res.append(self.expression[0]) if self.precedence[self.expression[0]] >= self.precedence[self.expression[2].expression[0]]: res.append('(' + self.expression[2].__str__() + ')') else: res.append(self.expression[2].__str__()) return ' '.join(res) def generateExpression(numbers, minNumbers, maxNumbers, commutativeSibling): '''Generates an expression that uses numbers from the list numbers (at least minNumbers, at most maxNumbers) and that is greater than it's commutativeSibling (if it's not empty)''' if minNumbers == 1: for index, (n, used) in enumerate(numbers): if not used: res = ExpressionTree(numbers) res.expression = ('=', index) if commutativeSibling and res.lessThan(commutativeSibling): continue numbers[index][1] = True yield res numbers[index][1] = False if maxNumbers > 1: for op in ExpressionTree.operators: res = ExpressionTree(numbers) res.expression = [op, None, None] for res.expression[1] in generateExpression(numbers, 1, maxNumbers - 1, ()): # How many numbers are left for the second operand? used = res.expression[1].numbersUsed() minN = max(1, minNumbers - len(used)) maxN = maxNumbers - len(used) if ExpressionTree.operators[op]: cS = res.expression[1] else: cS = () for res.expression[2] in generateExpression(numbers, minN, maxN, cS): if commutativeSibling and res.lessThan(commutativeSibling): continue yield res def main(): for i in range(9): for j in range(i+1, 9): for k in range(j+1, 9): for l in range(k+1, 9): greetingPrinted = False; numbers = [[i+1.0, False], [j+1.0, False], [k+1.0, False], [l+1.0, False]] results = {} for exp in generateExpression(numbers, 4, 4, ()): value = exp.evaluate() try: if abs(value - round(value)) < 0.001: value = round(value) except TypeError: pass results.setdefault(value, []).append((str(exp), exp.multiplicity())) for value in sorted(results.keys()): try: if 0 <= value <= 30 and abs(value - round(value)) < 0.001 and len(results[value]) == 1: if not greetingPrinted: print print "==========================" print "i = %1d, j = %1d, k = %1d, l = %1d" % (i+1, j+1, k+1, l+1) print "==========================" greetingPrinted = True print "Only one expression for " + str(value) + ":", results[value][0][0] if results[value][0][1] > 1: print " (With multiplicity " + str(results[value][0][1]) + ")" except TypeError: pass main()