For a non-recursive solution, you can break down the problem in simpler components:
- tokenizing expressions into a partial RPN stack for specific operators
- building the RPN notation by successive refinement (using the tokenizer) until only values and operators remain
- executing the RPN stack to get the final result
The tokenize()
function breaks down an expression into first level tokens for a given set of equal-precedence operators. This returns a mini-RPN stack (only for those operators) with operands being any string between these operators. Tokens that are enclosed in parentheses will be processed on a next refinement pass (i.e. by removing the outermost parentheses)
def tokenize(exp,opers):
result = []
token,operator,enclosed,pLevel = "","",True,0
for c in exp+opers[0]: # extra operator to force out last operation
if c in opers and token and pLevel == 0:
if enclosed: token = token[1:-1] # remove enclosing parentheses
result.append(token)
if operator: result.append(operator)
operator,token,enclosed = c,"",True
else:
token += c
pLevel += (c=="(")-(c==")")
if c != ")" and pLevel==0: enclosed = False
return result
The 'RPN()' function breaks down the whole expression by iteratively refining the tokens in a global RPN stack.
def RPN(S):
result = [S.replace(" ","").replace("**","^")] # unrefined RPN
done = False
while not done: # iterative refinement loop
previous = result.copy()
for opers in (["+","-"],["*","/"],["^"]): # reversed precedence
result = [token for exp in result for token in tokenize(exp,opers)]
done = result == previous
if not done: break # refine more, restart operators
return result
The 'calc()' function takes the components of the RPN stack and executes each operation in order to obtain the resulting value
def calc(S):
values = []
for op in RPN(S): # execute RPN
if op == "+": values[-2:] = [values[-2] + values[-1]]
elif op == "-": values[-2:] = [values[-2] - values[-1]]
elif op == "*": values[-2:] = [values[-2] * values[-1]]
elif op == "/": values[-2:] = [values[-2] / values[-1]]
elif op == "^": values[-2:] = [values[-2] ** values[-1]]
else: values.append(float(op))
return values[0]
output:
print(RPN("44 / (7 + 4) * 5 - 17"))
# ['44', '7', '4', '+', '/', '5', '*', '17', '-']
print(calc("44 / (7 + 4) * 5 - 17"))
# 3.0
note that this simple approach does not support monadic negations such as 2**-3 which you would have to write as 2**(-3)