Created
June 28, 2013 18:53
-
-
Save technillogue/5887092 to your computer and use it in GitHub Desktop.
A more complicated recursive python calculator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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? "))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ 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 |
Hello!
I was wondering if I have your permission to use your calculator in my project. All credit will go to you, along with a link to your GitHub. The file will be modified slightly to allow math equations similar to4(8)
, and of course small spelling differences as well. Is this okay with you?
How did you get the 4(8+8) to work?
Sure! I'm curious what you're using it for
…On Thu, Jul 16, 2020 at 01:21 nachoregulardude ***@***.***> wrote:
***@***.**** commented on this gist.
------------------------------
("^", lambda x, y: x ^ y) this didn't work for me, instead, I used ("^",
lambda x, y: pow(x, y)), Great program by the way. Do I have your
permission to use this in my calculator? Also for GitHub.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<https://gist.github.com/5887092#gistcomment-3379139>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAHG4G46OLHFPK6XIZZZJYDR32E5DANCNFSM4IYOMZRA>
.
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.
thanks for your code very much
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you very much!