czheo / czheo.github.io

https://czheo.github.io
2 stars 1 forks source link

How to parse a math expression? #8

Open czheo opened 7 years ago

czheo commented 7 years ago

Given a math expression such as: 3 + 8 / (3 + 1), we'd like to obtain the result 5. https://en.wikipedia.org/wiki/Shunting-yard_algorithm

czheo commented 7 years ago
import re
import operator

OPS = {
    '+': 0,
    '-': 0,
    '*': 1,
    '/': 1,
    '%': 1,
    '^': 2,
    'u': 3, # unary -
}

OP_FUNCS = {
    '+': operator.add,
    '-': operator.sub,
    '*': operator.mul,
    '/': operator.truediv,
    '%': operator.mod,
    '^': operator.pow,
    'u': operator.sub,
}

def tokenize(exp):
    return re.sub(r'([+\-*/^()%])', r' \1 ', exp).split()

def infix_to_postfix(toks):
    ret = []
    stack = []
    for i, tok in enumerate(toks):
        if tok in '+-' and (i==0 or toks[i-1] in OPS or toks[i-1]=='('):
        # HACK: unary +, -
            if tok == '-':
                ret.append('0')
                stack.append('u')
        elif tok in OPS:
        # binary ops
            if stack:
                while stack and OPS.get(stack[-1], -1) > OPS.get(tok):
                    ret.append(stack.pop())
            stack.append(tok)
        elif tok == '(':
            stack.append(tok)
        elif tok == ')':
            if not stack:
                raise Exception('Unmatched )')
            else:
                t = stack.pop()
                while t != '(':
                    ret.append(t)
                    t = stack.pop()
        else:
            ret.append(tok)
    while stack:
        ret.append(stack.pop())
    return ret

def evaluate(toks):
    stack = []
    for tok in toks:
        if tok in OP_FUNCS:
            snd = stack.pop()
            fst = stack.pop()
            stack.append(OP_FUNCS.get(tok)(fst, snd))
        else:
            stack.append(float(tok))
    return stack.pop()

def parse(exp):
    toks = tokenize(exp)
    print('infix:', toks)
    toks = infix_to_postfix(toks)
    print('postfix:', toks)
    return evaluate(toks)

def repl():
    exp = input('>> ')
    while exp != 'exit':
        print(parse(exp))
        exp = input('>> ')

if __name__ == '__main__':
    repl()