'''
October 2018 Puzzler
  10/3/18
  
You are equipped with two 2's, a single 3, and a single 6 along with
the ability to combine them using only addition, subtraction,
multiplication, division, and exponentiation.
Your job is to create each of the integers from 26 to 38.
You may use any number of parentheses to control the order of
operations and do not have to use all four of the numbers each
time.
For Example: 60 = 6 * (2^3 + 2) 
'''
from __future__ import print_function
import itertools as it

class Solution(object):
  '''
  A Solution is a solution to the puzzler.
  '''
  def __init__(self, value, equation):
    self.value = value          #Value is the result of the equation
    self.equation = equation    #The expression that evaluates to value
    self.hash = hash(equation)  #An unique integer for the expression

  def __repr__(self):
    '''
    Text representation of a Solution object.  For example:
      "27 = 3 + (2 + 2) * 6"
    '''
    return str(self.value) + " = " + self.equation

  def __eq__(self, other):
    '''
    Solutions are equal if the equations give identical hashes.
    '''
    return other.hash == self.hash

  def __ne__(self, other):
    return other.hash != self.hash

  def __hash__(self):
    return self.hash

  def __gt__(self, other):
    return self.value > other.value

  def __lt__(self, other):
    return self.value < other.value
  
def evaluate(equation):
  '''
    Evaluate equation.  If the result is 26 to 38, create a Solution object
    and add it to the answer set.
  '''
  try:                      #test for and ignore errors
    result = eval(equation)
    resultInt = int(result)
    #Test that the result is an integer in the range(26, 39)
    if result == resultInt and result in range(26, 39):
      #if result == resultInt and result in range(1, 100):
      answers.add(Solution(resultInt, equation))
  except:
    pass
  
         
if __name__ == "__main__":

  answers = set()                       #Create a Set of solutions to remove duplicates
  operands = it.permutations('2236')    #All possible arrangements of operands

  operators =[]                         #All possible arrangements of operators
  for a in ['+', '-', '*', '/', '**']:  # less all exponetiation (results too big)
    for b in ['+', '-', '*', '/', '**']:
      for c in ['+', '-', '*', '/', '**']:
        if not (a=="**" and b=="**" and c=="**"):
          operators.append([a, b, c])

    
  for d in operands:
    for t in operators:

      #Two term equations
      eqn = str(d[0]) + " " + str(t[0]) + " " + str(d[1])
      evaluate(eqn)

      #Three term equations
      eqn = str(d[0]) + " " + str(t[0]) + " " + str(d[1])  \
            + " " + str(t[1]) + " " + str(d[2])
      evaluate(eqn)

      eqn = "(" + str(d[0]) + " " + str(t[0]) + " " + str(d[1]) + ")" \
            + " " + str(t[1]) + " " + str(d[2])
      evaluate(eqn)
      
      eqn = str(d[0]) + " " + str(t[0]) + " " + "(" + str(d[1]) \
            + " " + str(t[1]) + " " + str(d[2]) + ")"
      evaluate(eqn)

      #Four term equations
      eqn = str(d[0]) + " " + str(t[0]) + " " + str(d[1])  \
            + " " + str(t[1]) + " " + str(d[2]) \
            + " " + str(t[2]) + " " + str(d[3])
      evaluate(eqn)

      eqn = "(" + str(d[0]) + " " + str(t[0]) + " " + str(d[1]) \
            + ") " + str(t[1]) + " " + str(d[2]) \
            + " " + str(t[2]) + " " + str(d[3])
      evaluate(eqn)

      eqn = str(d[0]) + " " + str(t[0]) + " (" + str(d[1])  \
            + " " + str(t[1]) + " " + str(d[2]) \
            + ") " + str(t[2]) + " " + str(d[3])
      evaluate(eqn)

      eqn = str(d[0]) + " " + str(t[0]) + " " + str(d[1])  \
            + " " + str(t[1]) + " (" + str(d[2]) \
            + " " + str(t[2]) + " " + str(d[3]) + ")"
      evaluate(eqn)

      eqn = "(" + str(d[0]) + " " + str(t[0]) + " " + str(d[1])  \
            + " " + str(t[1]) + " " + str(d[2]) \
            + ") " + str(t[2]) + " " + str(d[3])
      evaluate(eqn)

      eqn = str(d[0]) + " " + str(t[0]) + " (" + str(d[1])  \
            + " " + str(t[1]) + " " + str(d[2]) \
            + " " + str(t[2]) + " " + str(d[3]) + ")"
      evaluate(eqn)

      eqn = "(" + str(d[0]) + " " + str(t[0]) + " " + str(d[1])  \
            + ") " + str(t[1]) + " (" + str(d[2]) \
            + " " + str(t[2]) + " " + str(d[3]) + ")"
      evaluate(eqn)



  answers = sorted(answers)

  #Print the Solutions
  for answer in answers:
    print(answer)

  print("Number of solutions: ", len(answers))