Skip to content

Instantly share code, notes, and snippets.

@calvinfroedge
Last active August 29, 2015 14:25
Show Gist options
  • Save calvinfroedge/ca1168a3416a60d363e8 to your computer and use it in GitHub Desktop.
Save calvinfroedge/ca1168a3416a60d363e8 to your computer and use it in GitHub Desktop.
Math parser in Python
'''
"operator":[precedence, associativity]
'''
operators = {
"#":[5, "r", lambda x:x*-1], #unary minux
"^":[4, "r", lambda x,y: pow(x, y)],
"*":[3, "l", lambda x,y: x*y],
"/":[3, "l", lambda x,y: x/y],
"+":[2, "l", lambda x,y: x+y],
"-":[2, "l", lambda x,y: x-y]
}
'''
Parse spring expression into RPN and then solve it, using shunting yard algorithm
'''
class ExpressionSolver:
def __init__(self, expression):
self.expression = expression.replace(" ", "") #replace all whitespace
self.rpn = []
self.output = []
self.operators = []
self.index = 0
self.length = len(self.expression)
self.solution = None
def is_number(self, s):
try:
float(s)
return True
except ValueError:
return False
def lookahead(self):
self.index += 1
def consume(self):
value = ""
while self.index < self.length and self.curr() is not " " and self.curr() not in operators and self.curr() not in ["(", ")"]:
value += self.curr()
#Move the current pointer up
self.lookahead()
return value
def curr(self):
return self.expression[self.index]
def precedence(self, operator):
return operators[operator][0]
def assoc(self, operator):
return operators[operator][1]
def op_to_rpn(self):
self.rpn.append(self.operators.pop())
def top(self):
if not self.operators:
return None
return self.operators[len(self.operators)-1]
def unary(self):
val = self.expression[self.index]
if val is not "-":
return False
if self.index == 0:
return True
prev = self.expression[self.index-1]
if prev in operators or prev == "(":
return True
return False
'''
Parse an algebraic expression in RPN
'''
def parse(self):
while self.index < self.length:
curr = self.curr()
if self.is_number(curr):
self.rpn.append(self.consume())
elif curr is "(":
self.operators.append(curr)
self.lookahead()
elif curr is ")":
paren_end = self.index
while(self.operators and self.top() != "("):
self.op_to_rpn()
if self.top() == "(":
self.operators.pop()
if self.top() in operators:
self.op_to_rpn()
else:
raise Exception("Closing parenthesis at position {0} unmatched!".format(str(paren_end)));
self.lookahead()
elif curr in operators:
if e.unary():
self.operators.append('#')
else:
if self.operators:
if self.top() in operators:
p_top = self.precedence(self.top())
p_cur = self.precedence(curr)
if p_cur < p_top or p_cur == p_top:
if(self.assoc(curr) == 'l'):
self.op_to_rpn()
elif(self.assoc(curr) == 'r' and p_cur < p_top):
self.op_to_rpn()
self.operators.append(curr)
self.lookahead()
else:
self.lookahead()
while self.operators:
self.op_to_rpn()
'''
Solve RPN
'''
def solve(self):
queue = self.rpn
while queue:
item = queue[0]
queue = queue[1:]
if self.is_number(item):
self.output.append(float(item))
else:
fn = operators[item][2]
if item == "#":
popped = fn(self.output.pop())
self.output.append(float(popped))
else:
[b,a] = [self.output.pop(), self.output.pop()]
self.output.append(float(fn(a,b)))
self.solution = self.output.pop()
return self.solution
'''
Format the solution
'''
def format(self, precision):
as_int = int(self.solution)
if as_int == self.solution:
return as_int
else:
return round(self.solution, precision)
'''
Tests
'''
e = ExpressionSolver('2+2')
assert(e.consume() == '2')
e = ExpressionSolver('223')
assert(e.consume() == '223')
e = ExpressionSolver('+')
assert(e.consume() == '')
e = ExpressionSolver('2+2')
e.parse()
assert(e.rpn == ['2', '2', '+'])
e = ExpressionSolver('2+2*3')
e.parse()
assert(e.rpn == ['2', '2', '3', '*', '+'])
assert(e.solve() == 8)
e = ExpressionSolver('2^3')
e.parse()
assert(e.solve() == 8)
e = ExpressionSolver('6/2')
e.parse()
assert(e.solve() == 3)
e = ExpressionSolver('2')
e.parse()
assert(e.solve() == 2)
e = ExpressionSolver('-2')
assert(e.unary())
e = ExpressionSolver('2--2')
e.lookahead()
e.lookahead()
assert(e.unary())
e = ExpressionSolver('(2)-2')
e.lookahead()
e.lookahead()
e.lookahead()
assert(not e.unary())
e = ExpressionSolver('(-2)')
e.lookahead()
assert(e.unary())
e = ExpressionSolver('(-2)')
e.parse()
assert(e.solve() == -2)
ex1 = "250*14.3"
ex2 = "3^6 / 117"
ex3 = "(2.16 - 48.34)^-1"
ex4 = "(59 - 15 + 3*6)/21"
e = ExpressionSolver(ex4)
e.parse()
e.solve()
print e.format(5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment