3
\$\begingroup\$

Please feel free to review my countdown numbers game solver. Posted here too for people working on the same problem. For clarity the print statement "Eval..." means the program has used BODMAS on an entire mathematical statement (e.g: 1*3+10 = 31)

"Running total" means the program recalculates the total after each move (e.g: 1*3+10 = 13 because 1*3 = 3, 3+10 = 13)

import re
def unpack(method):
    string = method
    special = ["*","+","-","/"]
    list_sum = []
    list_special = []
    numbers = (re.findall(r"[\w']+", string))
    for char in string: 
        if char in special: 
            list_special.append(char)

    for index in range (len(numbers)-1):
        to_eval = numbers[index] + list_special[index] + numbers[index+1]
        list_sum.append(f'{to_eval} = {eval(to_eval)}')
        numbers[index+1] = str(eval(to_eval))

    return list_sum

def evaluate (method,running_sum):
    global clear_status 
    if eval(method) == target:
        if method not in list_methods:
            list_methods.append(method)
            print (f'Eval: {method}')
            clear_status = True 
        return True 
    if running_sum == target:
        if method not in list_methods:
            list_methods.append(method)
            print (f'Running sum: {unpack(method)}')
            clear_status = True 
        return True
    clear_status = False 
    return False

def new_total (total,item,operation):
    if operation == "+": return total + item
    if operation == "-": return total - item
    if operation == "*": return total * item
    if operation == "/" and item != 0: return total / item 
    return ""

def solve (array,total=0,method="",list_method=[]):
    if len(array) == 0:
        return 

    for (index,item) in enumerate(array):
        #Set methods and totals to "":
        add_method, sub_method, mul_method, div_method = "", "", "", ""
        add_total, sub_total, mul_total, div_total = 0, 0, 0,0

        #Assign methods and totals to a list:
        methods = [add_method, sub_method, mul_method, div_method]
        totals = [add_total, sub_total, mul_total, div_total]
        str_func = ["+", "-", "*", "/"]

        #Create new array
        remaining = array[:index] + array[index+1:]

        #Sets new totals and new "methods"
        for index_1 in range (len(methods)):
            if method =="":
                if str_func[index_1] != "/" and str_func[index_1] != "*" and str_func[index_1] != "-":
                    methods[index_1] = str(array[index])
                    totals[index_1] = new_total(total, item, str_func[index_1])
            else:
                methods[index_1] = method + str_func[index_1] + str(array[index])
                totals[index_1] = new_total(total, item, str_func[index_1])


        #Evaluates each total and method
        for index_2 in range (len(methods)):
            try: 
                if evaluate(methods[index_2], totals[index_2]):
                    if clear_status == True: 
                        methods[index_2]=""
                        totals[index_2]=0
                    return 
            except Exception as e:
                pass

        #Recursively calculates next move
        for index_3 in range (len(methods)):
            try:
                solve(remaining, total= totals[index_3],method= methods[index_3])
            except Exception as e:
                pass

str_array = input("Please enter the starting numbers, separated by commas: ")
array = array=[int(item.strip()) for item in str_array.split(",")]
target = int(input("Please enter a target value: "))
print (f'Solutions for {array} to reach {target}')
list_methods = []
solve(array)
if list_methods == []: 
    print ("Unsolvable countdown problem")
to_close = input("Press any key to exit...")
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$
  • Docstrings: You should include a docstring at the beginning of every method/class/module you write. This will help any documentation indentify what your code is supposed to do.
  • solve(): My biggest problem with this method was the beginning. You create eight different variables for your methods and total, then put them into a list. Instead of having these eight one time use variables, just set the default values inside the list. Since you don't work with any of the eight variables after putting them into the list, they are obsolete, so can be removed.
  • Variable/Parameter/Method Spacing: There shouldn't be spaces between the method name and the opening parentheses.
  • Parameter Reassignment: In unpack you have a passed parameter method. You then reassign that parameter to a variable string. This is very unnecessary. You have simply have the parameter string, so you're working directly with what is passed, instead of taking the time and trouble to reassign to a variable.
  • Use of global: It's not recommended to use global variables in python, or any language. This StackOverflow answer provides more insight.
  • Boolean Comparison: Instead of checking if variable == True, you can simply check the variable itself, like so: if variable:.
  • Unused Exceptions (as e): If you don't intend to work with an exception that you catch, it is unnecessary to have the extra code as e, since that will be a variable you never use. You can simply remove that code.
  • Unused Parameters: In solve, you never use the list_method=[] that is passed. You should remove any parameters you don't use, you risk adding complexity and reducing readability.
  • Constants Naming: Any constants in your code should be UPPERCASE, so make it clear to anyone reading your code, including yourself, that they are constant variables.
  • in list vs in [...]: You create a list of operators for the sole purpose of checking if an operator is in that list of operators. This is unnecessary. You can simplify your code by creating an anonymous list to check containment. This reduces the amount of variables in your code, increasing readability and reducing complexity.
  • if name == main guard: Any code that isn't contained in a method or class should be contained in a if __name__ == '__main__' guard. This will prevent any of that code from being executed if you decide to import the file, for other uses.
  • _ for unused variables: Your last line of code is a variable that allows the user to exit when they want, by pressing any key. You create a variable for this purpose alone. Since you never use this variable, and it's only used to exit the program, you can use a _, to make it clear that that variable is to be, essentially, ignored.

Updated Code

"""
Module Docstring:
Explanation of your code goes here
"""

import re

def unpack(string):
    """ Unpacks the passed `string` """
    list_sum = []
    list_special = []
    numbers = (re.findall(r"[\w']+", string))
    for char in string:
        if char in ["*", "+", "-", "/"]:
            list_special.append(char)

    for index in range(len(numbers) - 1):
        to_eval = numbers[index] + list_special[index] + numbers[index + 1]
        list_sum.append(f'{to_eval} = {eval(to_eval)}')
        numbers[index + 1] = str(eval(to_eval))

    return list_sum

def evaluate(method, running_sum):
    """ Evaluates the passed `method` """
    if eval(method) == TARGET:
        if method not in LIST_METHODS:
            LIST_METHODS.append(method)
            print(f'Eval: {method}')
            clear_status = True
        return True
    if running_sum == TARGET:
        if method not in LIST_METHODS:
            LIST_METHODS.append(method)
            print(f'Running sum: {unpack(method)}')
            clear_status = True
        return True
    clear_status = False
    return False

def new_total(total, item, operation):
    """ Determines the operator and returns the new total """
    if operation == "+":
        return total + item
    if operation == "-":
        return total - item
    if operation == "*":
        return total * item
    if operation == "/" and item != 0:
        return total / item
    return ""

def solve(array, total=0, method=""):
    """ Solves the passed numbers and target """
    if not array:
        return

    for index, item in enumerate(array):

        #Assign methods and totals to a list:
        methods = ["", "", "", ""]
        totals = [0, 0, 0, 0]
        str_func = ["+", "-", "*", "/"]

        #Create new array
        remaining = array[:index] + array[index+1:]

        #Sets new totals and new "methods"
        for index_1 in range(len(methods)):
            if method == "":
                if str_func[index_1] != "/" and str_func[index_1] != "*" and str_func[index_1] != "-":
                    methods[index_1] = str(array[index])
                    totals[index_1] = new_total(total, item, str_func[index_1])
            else:
                methods[index_1] = method + str_func[index_1] + str(array[index])
                totals[index_1] = new_total(total, item, str_func[index_1])

        #Evaluates each total and method
        for index_2, value_2 in enumerate(methods):
            try:
                if evaluate(value_2, totals[index_2]):
                    if clear_status:
                        methods[index_2] = ""
                        totals[index_2] = 0
                    return
            except Exception:
                pass

        #Recursively calculates next move
        for index_3, value_3 in enumerate(methods):
            try:
                solve(remaining, total=totals[index_3], method=value_3)
            except Exception:
                pass

if __name__ == '__main__':
    clear_status = None
    STR_ARRAY = input("Please enter the starting numbers, separated by commas: ")
    ARRAY = ARRAY = [int(item.strip()) for item in STR_ARRAY.split(",")]
    TARGET = int(input("Please enter a target value: "))
    print(f'Solutions for {ARRAY} to reach {TARGET}')
    LIST_METHODS = []
    solve(ARRAY)
    if LIST_METHODS == []:
        print("Unsolvable countdown problem")
    _ = input("Press any key to exit...")
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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