Skip to content

Instantly share code, notes, and snippets.

@technillogue
Created June 28, 2013 18:53
Show Gist options
  • Save technillogue/5887092 to your computer and use it in GitHub Desktop.
Save technillogue/5887092 to your computer and use it in GitHub Desktop.
A more complicated recursive python calculator
from collections import OrderedDict
#reverse order of operations
#I didn't have to use an OrderedDict, but it's cute
operations = OrderedDict([
("+", lambda x, y: x + y),
("-", lambda x, y: x - y),
("/", lambda x, y: x / y),
("*", lambda x, y: x * y),
("^", lambda x, y: x ^ y)
])
symbols = operations.keys()
def lex(expr):
"""
seperates numbers from symbols, recursively nests parens
"""
tokens = []
while expr:
char, *expr = expr
#char is equal to the first charecter of the expression, expr is equal
#to the rest of it
if char == "#":
#the rest of the line is a comment
break
if char == "(":
try:
paren, expr = lex(expr)
tokens.append(paren)
#expr is what's after the end of the paren, we'll just continue
#lexing after that''
except ValueError:
raise Exception("paren mismatch")
elif char == ")":
return tokens, expr
#returns the tokens leading up to the to the paren and the rest of
#the expression after it
elif char.isdigit() or char == ".":
#number
try:
if tokens[-1] in symbols:
tokens.append(char) #start a new num
elif type(tokens[-1]) is list:
raise Exception("parens cannot be followed by numbers")
#no support for 5(1+1) yet
else:
tokens[-1] += char #add to last num
except IndexError:
#if tokens is empty
tokens.append(char) #start first num
elif char in symbols:
tokens.append(char)
elif char.isspace():
pass
else:
raise Exception("invalid charecter: " + char)
return tokens
def evaluate(tokens):
for symbol, func in operations.items():
#try to find an operation to eval in order
try:
pos = tokens.index(symbol)
#split the tokens by the operation and eval that
leftTerm = evaluate(tokens[:pos])
rightTerm = evaluate(tokens[pos + 1:])
return func(leftTerm, rightTerm)
#incidentially, return immediatly breaks all loops within the
# function
except ValueError:
pass
#index raises ValueError when it's not found
if len(tokens) is 1:
try:
#it must be a number
return float(tokens[0])
except TypeError:
#if it's not a number
return evaluate(tokens[0])
else:
raise Exception("bad expression: " + tokens)
def calc(expr):
return evaluate(lex(expr))
while 1:
print(calc(input("Input? ")))
$ python3 calc.py
Input? 1 + 1 #simple operations, comments
2.0
Input? 0.1-0.5#floats and negatives, irrelevant whitespace
-0.4
Input? 4 + 4 / 2 - 1 #order of operations is respected
5.0
Input? (4-4 + (3-2) * (((((((7)))))))) # complex parens
7.0
@technillogue
Copy link
Author

technillogue commented Jul 18, 2020 via email

@gitdev-bash
Copy link

Hello, i have fixed your project. There was a issue with exponential calculation:

Input? 2^2
Traceback (most recent call last):
  File "/home/kuflierl/Software/Test/py/recrusive-calc.py", line 89, in <module>
    print(calc(input("Input? ")))
  File "/home/kuflierl/Software/Test/py/recrusive-calc.py", line 86, in calc
    return evaluate(lex(expr))
  File "/home/kuflierl/Software/Test/py/recrusive-calc.py", line 69, in evaluate
    return func(leftTerm, rightTerm)
  File "/home/kuflierl/Software/Test/py/recrusive-calc.py", line 11, in <lambda>
    ("^", lambda x, y: x ^ y)
TypeError: unsupported operand type(s) for ^: 'float' and 'float'

In python we use ** for exponential calculations. This should fix it:

#!/bin/python3
from collections import OrderedDict

#reverse order of operations
#I didn't have to use an OrderedDict, but it's cute
operations = OrderedDict([
    ("+", lambda x, y: x + y),
    ("-", lambda x, y: x - y),
    ("/", lambda x, y: x / y),
    ("*", lambda x, y: x * y),
    ("^", lambda x, y: x ** y)
])
    
symbols = operations.keys()

def lex(expr):
    """
    seperates numbers from symbols, recursively nests parens
    """
    tokens = []
    while expr:
        char, *expr = expr
        #char is equal to the first charecter of the expression, expr is equal
        #to the rest of it
        if char == "#":
            #the rest of the line is a comment
            break
        if char == "(":
            try:
                paren, expr = lex(expr)
                tokens.append(paren)
                #expr is what's after the end of the paren, we'll just continue
                #lexing after that''
            except ValueError:
                raise Exception("paren mismatch")
        elif char == ")":
            return tokens, expr
            #returns the tokens leading up to the to the paren and the rest of 
            #the expression after it
        elif char.isdigit() or char == ".":
            #number
            try:
                if tokens[-1] in symbols:
                    tokens.append(char) #start a new num
                elif type(tokens[-1]) is list:
                    raise Exception("parens cannot be followed by numbers")
                    #no support for 5(1+1) yet
                else:
                    tokens[-1] += char #add to last num
            except IndexError:
                #if tokens is empty
                tokens.append(char) #start first num
        elif char in symbols:
            tokens.append(char)
        elif char.isspace():
            pass
        else:
            raise Exception("invalid charecter: " + char)
    return tokens

def evaluate(tokens):
    for symbol, func in operations.items():
        #try to find an operation to eval in order
        try:
            pos = tokens.index(symbol)
            #split the tokens by the operation and eval that
            leftTerm = evaluate(tokens[:pos])
            rightTerm = evaluate(tokens[pos + 1:])
            return func(leftTerm, rightTerm)
            #incidentially, return immediatly breaks all loops within the
            # function
        except ValueError:
            pass
            #index raises ValueError when it's not found
    if len(tokens) is 1:
        try:
            #it must be a number
            return float(tokens[0])
        except TypeError:
            #if it's not a number
            return evaluate(tokens[0])
    else:
        raise Exception("bad expression: " + tokens)

while 1:
    print(evaluate(lex(input("Input? "))))

BTW i shortened your project a bit.

@thanhhung0112
Copy link

thanks for your code very much

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment