【问题标题】:calculator expression evaluator - prefix vs postfix notation?计算器表达式评估器 - 前缀与后缀表示法?
【发布时间】:2020-09-04 16:19:26
【问题描述】:

用于为以下表达式编写自己的计算器(表达式评估器):

3+2*5

7+(8/2)*5

3*5+8*7

我的印象是,实现这一点的唯一明智方法是转换为前缀表示法或后缀表示法,然后从那里进行评估。

我在不久前使用后缀表示法完成了这项工作,它似乎工作得很好。当时我从某个地方(现在不记得在哪里)得到了这个想法,后缀表示法更符合这个目的。我没有用前缀表示法尝试过。

为此任务选择前缀表示法与后缀表示法有哪些优点/缺点?对于一个典型的面试问题(例如https://leetcode.com/problems/basic-calculator-ii/),什么是更好的选择?为什么?

附: Python 的eval 是使用前缀、后缀还是完全不同的方法?我的印象是,这是 Python 的 eval 的来源 https://github.com/python/cpython/blob/master/Python/ceval.c,但目前还不清楚一般策略是什么。

【问题讨论】:

  • 如何实现前缀评估?执行操作之前需要参数!
  • @YvesDaoust 您将递归调用以评估参数,然后执行运算符。
  • @MattTimmermans:谢谢,这是后缀。
  • 后缀求值是迭代的——将操作数压入堆栈,然后处理运算符

标签: algorithm data-structures


【解决方案1】:

无需先转换为前缀或后缀。如果您只打算评估一次或几次,那么您可以在解析时进行。我通常使用这样的递归下降:

import re

def evalExpr(infixStr):
    # precedences for infix operators
    precs = {'+':0 , '-':0, '/':1, '*':1, '^':2}
    # functions of operators
    funcs = {
        '+': (lambda a,b: a+b),
        '-': (lambda a,b: a-b),
        '/': (lambda a,b: a/b),
        '*': (lambda a,b: a*b),
        '^': (lambda a,b: a**b)
    }

    # divide string into tokens
    tokens = re.split(r' *([\(\)\+\-\*\^/]) *', infixStr)
    tokens = [t for t in tokens if t!='']

    # current parse position
    pos = 0

    # evaluate infix expression at the parse point,
    # processing only operators above a given precedence
    def eval2(minprec,closer=None):
        nonlocal pos, tokens
        # first we need a number or parenthesized expression
        if (pos >= len(tokens)):
            raise Exception("Unexpected end")
        if (tokens[pos]=="("):
            pos += 1
            val = eval2(0,")")
            pos += 1
        else:
            val = float(tokens[pos])
            pos += 1
        # gather operators that consume the value
        while pos < len(tokens):
            op = tokens[pos]
            if op == closer:
                return val
            prec = precs.get(op)
            if prec == None:
                raise Exception("operator expected. got " + op)
            if prec<minprec: # precedence too low for us
                break
            pos += 1 # operator OK

            # get the argument on the operator's right
            # this will go to the end, or stop at an operator
            # with precedence <= prec
            arg2 = eval2(prec+1,closer)
            val = (funcs[op])(val, arg2)
        if closer != None:
            raise Exception("Expected " + closer)
        return val

    return eval2(0)

print(evalExpr("5+3*4^2+1"))  # prints 54.0
print(evalExpr("7+(8/2)*5"))  # prints 27.0
print(evalExpr("3*5+8*7"))    # prints 71.0

这种递归下降风格是写了很多表达式解析器的结果,现在我几乎总是用这种方式解析表达式。它与表达式解析器一样简单,并且可以轻松添加一元运算符和不同类型的括号,以及像赋值运算符一样从右到左关联的运算符。

秘诀是eval2的签名,它可以很容易地实现优先规则。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-13
    相关资源
    最近更新 更多