Number Expression Solver: 4, 31

By Charlie and Logan

Base Value:
Target:
31 = ((4!+sqrt(4))+(sqrt(4)/.4))
31 = (4!+((4+4!)/4))
31 = (((4!-sqrt(4))/.4)-4!)
31 = (sqrt(4)+(4!+(sqrt(4)/.4)))
31 = ((sqrt(4)+(4!/.4))/sqrt(4))

class NumWithExplanation:
    def __init__(self, value, count, op, left, right, value_str = None):
        self.value = value
        self.count = count
        self.op = op
        self.left = left
        self.right = right
        self.value_str = value_str

    def equals(self, other):
        if self.value != other.value:
            return False
        if self.count !=other.count:
            return False
        if self.op !=other.op:
            return False
        if self.left and self.right and other.left and other.right and (
            (self.left.value != other.left.value and self.right.value != other.right.value)
            and
             (self.left.value != other.right.value and self.right.value != other.left.value)):
            return False
        
        return True
        
    def to_string(self):
        if self.count == 1:
            if self.value_str:
                return self.value_str
            else:
                return '4'
        else:
            s = "(%s%s%s)" % (self.left.to_string(), self.op, self.right.to_string())
            if self.value_str =="sqrt":
                s = 'sqrt(%s)' % (s)
            return s
        
    
    def to_full_string(self):
        s = '%d = %s'% (self.value, self.to_string())
        return s
        
def add_to_dict(d,value, num_with_explanation, n_sqrts = 0):
    if value!=int(value):
        return
    value=int(value)
    if value not in d:
        d [value] = []
    for elem in d [value]:
        if elem.equals(num_with_explanation):
            return
    d[value].append(num_with_explanation)
    if value>0 and n_sqrts == 0:
        if math.sqrt(value) == int(math.sqrt(value)):
            add_to_dict(d, math.sqrt(value),
                NumWithExplanation(
                    math.sqrt(value),
                    num_with_explanation.count,
                    num_with_explanation.op,
                    num_with_explanation.left,
                    num_with_explanation.right,
                    'sqrt'), 1)
                

def combine(d, d1, d2):
    for v1 in d1:
        solutions1=d1[v1]
        for solution1 in solutions1:
            for v2 in d2:
                solutions2 = d2 [v2]
                for solution2 in solutions2:
                    count=solution1.count + solution2.count
                    
                    value=solution1.value+solution2.value
                    add_to_dict(d, value,
                      NumWithExplanation(value, count, "+", solution1, solution2))
                                
                    value=solution1.value - solution2.value
                    add_to_dict(d, value,
                      NumWithExplanation(value, count, "-", solution1, solution2))
                                
                    value=solution2.value - solution1.value
                    add_to_dict(d, value,
                      NumWithExplanation(value, count, "-", solution2, solution1))
                                
                    value=solution1.value * solution2.value
                    add_to_dict(d, value,
                      NumWithExplanation(value, count, "*", solution1, solution2))
                                
                    if solution2.value != 0:
                        value=solution1.value / solution2.value
                        add_to_dict(d, value,
                          NumWithExplanation(value, count, "/", solution1,
                                solution2))
                        
                    if solution1.value != 0:
                        value=solution2.value / solution1.value
                        add_to_dict(d, value,
                          NumWithExplanation(value, count, "/", solution2,
                                                      solution1))
                        
                    



base = NumWithExplanation(BASE_VALUE, 1, 'identity', None, None)
base_value_factorial = NumWithExplanation(math.factorial(BASE_VALUE),
                            1, 'identity', None, None, '%d!' % BASE_VALUE)
base_value_decimal = NumWithExplanation(BASE_VALUE/10,
                            1, 'decimal', None, None, '.%d' % BASE_VALUE)
dicts = {}

dicts[1] = {BASE_VALUE: [base],
            math.factorial(BASE_VALUE): [base_value_factorial],
            BASE_VALUE/10: [base_value_decimal]
            }

if int(math.sqrt(BASE_VALUE)) == math.sqrt(BASE_VALUE):
    dicts[1][math.sqrt(BASE_VALUE)] = [NumWithExplanation(math.sqrt(BASE_VALUE),
             1, 'sqrt', None, None, 'sqrt(%d)' % BASE_VALUE)]

dicts[2] = {}
combine(dicts[2], dicts[1], dicts[1]);
if DOUBLE not in dicts[2]:
    dicts[2][DOUBLE] = []
dicts[2][DOUBLE].append(NumWithExplanation(DOUBLE, 2, ' ', base, base, '%d' % (DOUBLE)))

dicts[3] = {}
combine(dicts[3], dicts[1], dicts[2]);

dicts[4] = {}
combine(dicts[4], dicts[2], dicts[2]);
combine(dicts[4], dicts[1], dicts[3]);

if TARGET in dicts[4]:
    for solution in dicts[4][TARGET]:
        print(solution.to_full_string())