Trying to figure out how to handle math expression including parentheses

176 views Asked by At

so I'm currently trying to write a code that basically knows how to deal with mathematical expressions, it has some other little features such as calculating average, getting min number out of 2, getting max number out of 2, etc etc. Issue is, it currently missing the abillity to support parentheses. I need it to deal it even multiple parentheses, or parentheses inside parentheses. How would you suggesting achieving it in a "simple" and easy to understand way?

NOTE: I'm a beginnner, trying to climb my way up :) this is my code:

def parse_expression(expr):
    elements = list(expr.replace(" ", ""))
    exercise = []
    operators = ('+', '-', '*', '/', '$', '&', '@') #### @ = Average, & = Min, $ = Max
    i = 0

    while i < len(elements):
        if elements[i].isdigit() or (elements[i] == '-' and (i == 0 or elements[i - 1] in operators)):
            num = elements[i]

            while i + 1 < len(elements) and (elements[i + 1].isdigit() or elements[i + 1] == '.'):
                i += 1
                num += elements[i]

            exercise.append(float(num))
        elif elements[i] in operators:
            exercise.append(elements[i])
        i += 1

    return exercise

def calculate_result(exercise):
    k = 0
    while k < len(exercise):
        if exercise[k] == '$' or exercise[k] == '&' or exercise[k] == '@':
            operator = exercise[k]
            left_num = exercise[k - 1]
            right_num = exercise[k + 1]
            if operator == '$':
                result = max(left_num, right_num)
            elif operator == '&':
                result = min(left_num, right_num)
            elif operator == '@':
                result = (left_num + right_num) / (2)

            exercise[k] = result
            exercise.pop(k + 1)
            exercise.pop(k - 1)
        else:
            k += 1

    k = 0
    while k < len(exercise):
        if exercise[k] == '*' or exercise[k] == '/':
            operator = exercise[k]
            left_num = exercise[k - 1]
            right_num = exercise[k + 1]
            if operator == '*':
                result = left_num * right_num
            elif operator == '/':
                try:
                    result = left_num / right_num
                except ZeroDivisionError:
                    print("ERROR: Can't divide by zero. ")
                    return None
            exercise[k] = result
            exercise.pop(k + 1)
            exercise.pop(k - 1)
            k -= 1
        k += 1

    k = 0
    while k < len(exercise):
        if exercise[k] == '+' or exercise[k] == '-':
            operator = exercise[k]
            left_num = exercise[k - 1]
            right_num = exercise[k + 1]
            if operator == '+':
                result = left_num + right_num
            elif operator == '-':
                result = left_num - right_num
            exercise[k] = result
            exercise.pop(k + 1)
            exercise.pop(k - 1)
        else:
            k += 1

    return round(exercise[0], 2)

while True:
    expr = input("calc> ")
    if expr in ('EXIT', 'Exit', 'exit'):
        print("Bye")
        break
    exercise = parse_expression(expr)
    if len([s for s in exercise if isinstance(s, float)]) == 0:
        print("Invalid input.")


    else:
        result = calculate_result(exercise)
        if result is not None:
            print(f"{expr} = {result}")
3

There are 3 answers

0
Daviid On

Inside parse_expression(), whenever you see an opening parenthesis start a new expression, then when you see the closing parenthesis send then send that to parse_expression()

expression = '2 + 3 * (4 + 5)'

new_expression = '4 + 5'

I'm assuming you're doing this for fun because python can interpret math strings with eval(exp)

so eval('2 + 3 * (4 + 5)') outputs 29, not sure how complex it can parse though.

Edit:

For a more advanced approach you can read on Shunting Yard algorithm, a method for parsing arithmetical or logical expressions, or a combination of both, specified in infix notation.

0
Scoffield On

As already mentioned, the "safe" way to do this would be checking given expression for smaller expression in parenthesis an passing it in the same function to parse.

The "easy" way would be using eval() function:

x = input()  # '5*(2+3)'

if  '(' in x and ')' in x:
    print(eval(x)) # 25
else:
    print('No "()" in expression ')

Heads-up: Try not to use eval() for anything but learning or fun, it is a very bad practice (More info: https://www.codiga.io/blog/python-eval/).

0
Amira Bedhiafi On

You may use a common approach to support '(' and ')' which a recursive function. Whenever a parenthesis is encountered, the function will call itself to solve the inner expression. T

Your current parse_expression can be modified to recognize '(' and ')' by adding a couple of lines.

elif elements[i] in operators or elements[i] in ('(', ')'):
    exercise.append(elements[i])

When you encounter an opening parenthesis '(', you want to find its closing counterpart ')'. Then, you want to evaluate everything inside those parentheses recursively.

def evaluate_parentheses(exercise):
    # While there's an opening parenthesis
    while '(' in exercise:
        open_idx = None
        close_idx = None

        # find the innermost opening parenthesis
        for idx, item in enumerate(exercise):
            if item == '(':
                open_idx = idx
            if item == ')':
                close_idx = idx
                break

        if open_idx is None or close_idx is None:
            print("Mismatched parentheses!")
            return None

        # Evaluate the expression inside the parentheses recursively
        inner_result = calculate_result(exercise[open_idx + 1: close_idx])

        # Replace the entire parenthetical expression with its result
        exercise[open_idx: close_idx + 1] = [inner_result]

    return exercise

Then, in your calculate_result, at the beginning:

def calculate_result(exercise):
    exercise = evaluate_parentheses(exercise)
    if exercise is None:
        return None