3
\$\begingroup\$

This is a Target number solution finder which I made with tkinter and is where the user enters a number they want and enters 4 other numbers to find the sum to make the number they want.

This is a screenshot on what the GUI looks like: enter image description here

This is the code for the project:

from tkinter import Tk, Frame, Label, Button, IntVar, Entry, Text, W, N, WORD, INSERT
from itertools import permutations,combinations_with_replacement



class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.pack()
        self.create_widgets() 


    def create_widgets(self):

        self.target_num = IntVar()
        self.num1 = IntVar()
        self.num2 = IntVar()
        self.num3 = IntVar()
        self.num4 = IntVar()       
        # self.title("target number solution")

        Label(self,image='', bg="white").grid(row=0, column=0, sticky=W)
        Label(self, text="Enter target number:", bg="black", fg="white", font="none 12 bold").grid(row=1, column=0, sticky=N)
        self.textentry1 = Entry(self, textvariable=self.target_num, width=20, bg="white")
        self.textentry1.grid(row=2, column=0, sticky=N)


        Label(self, text="Enter first number:",bg="black", fg="white", font="none 12 bold").grid(row=4, column=0, sticky=N)
        self.textentry2 = Entry(self, textvariable=self.num1, width=20, bg="white")
        self.textentry2.grid(row=5, column=0, sticky=N)


        Label(self, text="Enter second number:",bg="black", fg="white", font="none 12 bold").grid(row=6, column=0, sticky=N)
        self.textentry3 = Entry(self, textvariable=self.num2, width=20, bg="white")
        self.textentry3.grid(row=7, column=0, sticky=N)


        Label(self, text="Enter third number:",bg="black", fg="white", font="none 12 bold").grid(row=8, column=0, sticky=N)
        self.textentry3 = Entry(self, textvariable=self.num3, width=20, bg="white")
        self.textentry3.grid(row=9, column=0, sticky=N)


        Label(self, text="Enter fourth number:",bg="black", fg="white", font="none 12 bold").grid(row=10, column=0, sticky=N)
        self.textentry4 = Entry(self, textvariable=self.num4, width=20, bg="white")
        self.textentry4.grid(row=11, column=0, sticky=N)


        Button(self, text="Solve", width=6, command=self.solver).grid(row=12, column=0, sticky=N)
        self.output = Text(self, width=60, height=10, wrap=WORD, background="white")
        self.output.grid(row=13, column=0, columnspan=1, sticky=N)

    def solver(self):
        self.output.delete(1.0, INSERT)
        target = self.target_num.get()
        number1 = self.num1.get()
        number2 = self.num2.get()
        number3 = self.num3.get()
        number4 = self.num4.get()
        numbers = [number1, number2, number3, number4]
        operators = ["+","-","*","/"]
        groups    = ['X+X+X+X', 'X+X+(X+X)', 'X+(X+X)+X', '(X+X+X)+X', '(X+X)+X+X', 'X+(X+X+X)', '((X+X)+X)+X', 'X+(X+(X+X))', 'X+((X+X)+X)', '(X+X)+(X+X)', '(X+(X+X))+X']
        seen = set()
        for values in permutations(numbers,len(numbers)):
            for operCombo in combinations_with_replacement(operators,len(numbers)-1):
                for oper in permutations(operCombo,len(numbers)-1):
                    formulaKey = "".join(str(oper+values))
                    if formulaKey in seen: continue # ignore variations on parentheses alone
                    for pattern in groups:
                        formula = "".join(str(o)+str(p) for o,p in zip([""]+list(oper), pattern.split("+")))
                        formula = "".join(str(v)+str(p) for v,p in zip([""]+list(values),formula.split("X")))
                        try:
                            if eval(formula) == target:
                                Answer = formula,"=",target
                                seen.add(formulaKey)
                                #insert value in output Textbox
                                self.output.insert(INSERT, Answer) 
                                self.output.insert(INSERT, '\n') 
                                break
                            elif eval(formula) != target:
                                self.output.insert(INSERT, 'Solution could not be found')
                                break
                        except: pass

root = Tk()
app = Application(master=root)
app.master.title("target number solution")
app.mainloop()

I just want suggestions on how I can improve this project to make it better.

All suggestions will be greatly appreciated

\$\endgroup\$
4
\$\begingroup\$

I am concentrating on the solver method, since my sense for a good UX is close to null. I added inline comments for my changes.

But here some major changes:

  • Your code was not showing all combinations because you were breaking the loop after first item.
  • The two nested fors with combinations_with_replacement and permutations, does the same as product().
  • Defined groups and operators as constants (in uppercase) at the beginning of the code.
  • Used END instead of INSERT in output so it clears text of previous results.
  • Added formula_key right away and not in the condition, it was computing the same formula multiple times.
  • Snake case for naming variables convention in python.

Here the code (create_widgets and the constructor stay the same):

from tkinter import Tk, Frame, Label, Button, IntVar, Entry, Text, W, N, WORD, INSERT, END
from itertools import permutations, product

# Define constants
OPERATORS = ["+","-","*","/"]
GROUPS    = ['X+X+X+X', 'X+X+(X+X)', 'X+(X+X)+X', '(X+X+X)+X', '(X+X)+X+X', 'X+(X+X+X)', '((X+X)+X)+X', 'X+(X+(X+X))', 'X+((X+X)+X)', '(X+X)+(X+X)', '(X+(X+X))+X']

class Application(Frame):
    def __init__(self, master=None):
        # ...

    def create_widgets(self):
        # ...

    def solver(self):
        self.output.delete(1.0, END)  # END removes previous results instead of INSERT
        target = self.target_num.get()
        number1 = self.num1.get()
        number2 = self.num2.get()
        number3 = self.num3.get()
        number4 = self.num4.get()
        numbers = [number1, number2, number3, number4]
        seen = set()
        len_opr = len(numbers) - 1  # Compute once
        for values in permutations(numbers): # No need to specify len
            for oper in product(OPERATORS, repeat=len_opr):  # Product does what you want instead of two fors
                formula_key = "".join(str(oper + values))
                if formula_key in seen: continue # ignore variations for repeated numbers
                seen.add(formula_key)  # Add right away not in the condition
                # if only one operation, do not permute parenthesis
                filtered_groups = [GROUPS[0]] if len(set(oper)) == 1 else GROUPS
                for pattern in filtered_groups:
                    formula = "".join(str(o)+str(p) for o, p in zip([""] + list(oper), pattern.split("+")))
                    formula = "".join(str(v)+str(p) for v, p in zip([""] + list(values), formula.split("X")))
                    try:
                        if eval(formula) == target:
                            answer = formula, "=", target  # vars start by minuscule
                            #insert value in output Textbox
                            self.output.insert(INSERT, answer)
                            self.output.insert(INSERT, '\n')
                            result = True
                        # REMOVE this condition or it does not try all groups with parenthesis
                    except: pass
        if self.output.get("1.0", END)=="\n":
            self.output.insert(INSERT, 'Solution could not be found')
root = Tk()
app = Application(master=root)
app.master.title("target number solution")
app.mainloop()

If you have any questions, don't hesitate!

\$\endgroup\$
9
  • \$\begingroup\$ Thank you for your answer \$\endgroup\$ May 29 '19 at 11:47
  • \$\begingroup\$ I am getting this error: self.output.delete(1.0, END) NameError: name 'END' is not defined \$\endgroup\$ May 29 '19 at 11:54
  • \$\begingroup\$ Check the first line, it has to be imported \$\endgroup\$
    – palvarez
    May 29 '19 at 11:55
  • \$\begingroup\$ Thanks the error is gone but now I get another error:for oper in product(OPERATORS, repeat=len_opr): NameError: name 'product' is not defined \$\endgroup\$ May 29 '19 at 11:59
  • \$\begingroup\$ Check the second line, it has to be imported ;) \$\endgroup\$
    – palvarez
    May 29 '19 at 12:00
2
\$\begingroup\$

is it working now and i have an evil hack in there:

  • Same values inside a loop
  • You do have only 5 groups
  • Division by 0 must be escaped
  • Not building a string and then eval, just have list of functions, that makes code smaller and runs faster
  • No hashing need because results do not repeat unless you do many times the same digits combo...
  • Attention, evil hack: Generate combination of functions by increasing some digit and viewing its bits : 12, 34, 56 as digits from 0-3 what are function indexes.

There it is:

                    fa = self.ops [op_bits & 3]
                fb = self.ops [(op_bits & 12) >> 2 ]
                fc = self.ops [(op_bits & 48) >> 4 ]

enjoy the crap:

    from tkinter import Tk, Frame, Label, Button, IntVar, Entry, Text, W, N, WORD, INSERT


class Application(Frame):

    #extract constant fields:

    op_str = ['+', '-' , '*' , '/']

    ops = [lambda a,b : a+b if (a is not None and b is not None) else None,
                    lambda a,b : a-b if (a is not None and b is not None) else None, 
                    lambda a,b : a*b if (a is not None and b is not None) else None,
                    lambda a,b :  a/b if (b != 0 and a is not None and b is not None) else None, 
                   ]

    max_combinations = 1 << 6

    group_strs    = ['((%d%s%d)%s%d)%s%d', '%d%s(%d%s(%d%s%d))', '%d%s((%d%s%d)%s%d)', '(%d%s%d)%s(%d%s%d)', '(%d%s(%d%s%d))%s%d']

        #clumsy a lil bit
    groups = [lambda a,b,c,d,fa,fb,fc :  fc(fb(fa(a,b),c),d),
              lambda a,b,c,d,fa,fb,fc :  fa(a,fb(b,fc(c,d))),
            lambda a,b,c,d,fa,fb,fc :  fa(a,fc(fb(b,c),d)),
            lambda a,b,c,d,fa,fb,fc :  fb(fa(a,b), fc(c,d)),
            lambda a,b,c,d,fa,fb,fc :  fc(fa(a,fb(b,c)),d),


                          ]    


    def __init__(self, master=None):
        super().__init__(master)
        self.pack()
        self.create_widgets() 







    def create_widgets(self):

        self.entries = []  
        self.vars = [IntVar() for _ in range (5)]
        self.texts = ['Enter target number' , 
                      'Enter first number', 
                      'Enter second number', 
                      'Enter third number', 
                      'Enter fourth number']


        for index, var in enumerate(self.vars):
            Label(self, text=self.texts[index], bg="black", fg="white", font="none 12 bold").grid(row=2*index, column=0, sticky=N)
            e = Entry(self, textvariable= self.vars[index] , width=20, bg="white")
            e.grid(row=2*index+1, column=0, sticky=N)
            self.entries.append(e)

        Button(self, text="Solve", width=6, command=self.solver).grid(row=12, column=0, sticky=N)
        self.output = Text(self, width=60, height=10, wrap=WORD, background="white")
        self.output.grid(row=13, column=0, columnspan=1, sticky=N)   

    def solver(self):
        self.output.delete(1.0, INSERT)
        target = self.vars[0].get()
        numbers = [e.get() for e in self.vars[1:]]


        count = 0
        for gr_num, group in enumerate(self.groups):


            for op_bits in range(0,self.max_combinations):
                count += 1
                #evil hacks here
                fa = self.ops [op_bits & 3]
                fb = self.ops [(op_bits & 12) >> 2 ]
                fc = self.ops [(op_bits & 48) >> 4 ]
            # or even shorter like
            # fs = [(op_bits & (3 << x)) >> x for x in range(0,self.max_combinations) ]

                my_eval = group(*numbers, fa,fb,fc)
                print('my_eval' , my_eval, ' =' , *numbers, fa,fb,fc,  )
                if my_eval == target:
                    formula = self.group_strs[gr_num] % (numbers[0],self.op_str[op_bits & 3],numbers[1],self.op_str[(op_bits & 12) >> 2],numbers[2],self.op_str[(op_bits & 48) >> 4],numbers[3])
                    Answer = formula,"=",target , ' found in ' , count , 'iterations'
                    #seen.add(formulaKey)
                    #insert value in output Textbox
                    self.output.insert(INSERT, Answer) 
                    self.output.insert(INSERT, '\n')
                    return

        self.output.insert(INSERT, 'Solution could not be found in ' + str(count) +  ' iterations')






root = Tk()
app = Application(master=root)
app.master.title("target number solution")
app.mainloop()
\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your answer \$\endgroup\$ May 29 '19 at 11:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.