Last active
August 29, 2015 14:25
-
-
Save calvinfroedge/ca1168a3416a60d363e8 to your computer and use it in GitHub Desktop.
Math parser in Python
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
''' | |
"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