Bootstrap

Python实现简单表达式求值

问题来源于清华大学出版社1992年第二版《数据结构》,3.2节 表达式求值。书中采用了“算符优先法”并提供了算法的伪代码,本文是伪码的Python实现。

问题的简要描述(具体可参考《数据结构》)
本文仅讨论个位数的包含加减乘除小括号的表达式,并规定表达式以‘#’结束。
表达式实例:3*(7-2)#
不讨论表达式语法错误的情况

问题分析(具体可参考《数据结构》)
运算符和界限符统称为算符,我们将它们构成的集合命名为OP。
两个相继出现的运算符θ₁和θ₂的优先关系为:

θ₁\θ₂+-*/()#
+>><<<>>
->><<<>>
*>>>><>>
\>>>><>>
(<<<<<=
)>>>>>>
#<<<<<=

由于表达式以‘#’结束,为了算法简洁,在表达式的最左边虚设一个‘#’,构成一对。
为实现算符优先算法,可以使用两个工作栈。OPTR寄存运算符,OPND寄存操作数和运算结果。
算法的基本思想为:
1.置操作数栈为空栈,表达式起始符‘#’为运算符栈的栈底元素;
2.依次读入表达式中的字符,若是操作数则进OPND栈;若是运算符,则和OPTR栈的栈顶运算符比较优先权后,进行相应操作,直至表达式求值完毕。

算法的伪代码表示
FUNC exp_reduced:operandtype;
{OPTR:运算符栈,OPND:操作数栈,OP:运算符集合}
 INISTACK(OPTR);
 PUSH(OPTR,’#’);
 INSTACK(OPND);
{栈初始化,并在运算符栈的栈底压入表达式左端的虚设字符‘#’}
 read(w);{从终端接收一个字符}
WHILE NOT ((w=’#’) AND (GETTOP(OPTR)=’#’)) DO
  IF w NOT IN op THEN PUSH(OPND,w)
   ELSE CASE precede(GETTOP(OPTR),w) OF {比较优先权}
    ‘<’:[PUSH(OPTR,w); read(w)];{栈顶元素优先权低}
    ‘=’:[x:=POP(OPTR); read(w)];{脱括弧并接收下一个字符}
    '‘>’:[theta:=POP(OPTR); b=POP(OPND); a:=POP(OPND); PUSH(OPND,operate(a,theta,b))]
{退栈并将运算结果入栈}
   ENDC;
RETURN(GETTOP(OPND))
ENDF; {exp_reduced}

Python实现

import numpy


def is_int(x):
    try:
        int(x)
        return True
    except ValueError:
        return False
#自定义函数,用于判断字符是否为数字

def calculate(expression):
    change = {'+': 0, '-': 1, '*': 2, '/': 3, '(': 4, ')': 5, '#': 6}
    #将符号转换为数字,用于对应二维数组
    prior = numpy.array([
        [1, 1, -1, -1, -1, 1, 1],
        [1, 1, -1, -1, -1, 1, 1],
        [1, 1, 1, 1, -1, 1, 1],
        [1, 1, 1, 1, -1, 1, 1],
        [-1, -1, -1, -1, -1, 0, 2],
        [1, 1, 1, 1, 2, 1, 1],
        [-1, -1, -1, -1, -1, 2, 0]
    ])
    #用二维数组表示符号间的优先关系,即上文中的表格。
    #其中1为>;0为=;-1为<;2用于占位,也可以用于判断表达式错误。在代码中没有讨论表达式错误的情况。
    operator = ['#']
    number = []
    #operator为伪码中的OPTR栈,即运算符栈;number为OPND栈,即操作数栈。
    i = 0
    #通过下标遍历字符串
    while not (expression[i] == '#' and operator[-1] == '#'):
        if is_int(expression[i]):
            number.append(expression[i])
            i += 1
        elif prior[change[operator[-1]]][change[expression[i]]] == -1:
            operator.append(expression[i])
            i += 1
        elif prior[change[operator[-1]]][change[expression[i]]] == 0:
            operator.pop()
            i += 1
        elif prior[change[operator[-1]]][change[expression[i]]] == 1:
            op = operator.pop()
            b = number.pop()
            a = number.pop()
            number.append(str(eval(a + op + b)))
            #计算a和b通过op运算符后的结果。我直接使用了eval函数,如果不用,可以写四个判断进行计算。
            #需要注意的是eval接收的参数为字符串,要将计算得到的结果转化为字符再压入栈内
    return int(number[-1])
    #number内存放的是字符,需要转化为数字

while True:
    string = input("input:")
    if string == "quit":
        break
    else:
        print(calculate(string))
    #测试

个人想法
我在看到算符优先关系的表格的时候挺迷惑的,带入一个例子模拟计算步骤就比较好理解了。

对于算法的基础实现,感觉Python代码量少的优势完全没有体现出来,C++也是差不多这样写的。不过如果直接用eval的话,Python一行就可以出结果了。

参考资料
《数据结构》第二版 严蔚敏 吴伟民 编著 清华大学出版社 1992年6月 P45-47

;