Python 借助逆波兰表达式(后缀表达式)实现计算器

1,378 阅读3分钟

Python 借助逆波兰表达式(后缀表达式)实现简单计算器


0. 参考资料


1. 中缀表达式转后缀表达式

中缀表达式转后缀表达式的过程中需要两个主要存储结构。

  • 用于存放最终后缀表达式的列表 postfix
  • 用于存放运算符的栈 operators,可以使用 Python 中的列表实现

转换步骤:

  • 从左到右顺序扫描中缀表达式
  • 如果扫描到操作数,直接追加到后缀表达式 postfix
  • 如果扫描到运算符:
    • ( 左括号: 直接压栈到运算符栈 operators
    • ) 右括号: 依次弹出运算符栈中的元素,并放入到后缀表达式 postfix 中; 直到遇到 ( 左括号,把它弹出后丢弃,后缀表达式中不需要括号。
    • 加减乘除: 如果该运算符优先级高于栈顶元素,则将它压栈到 operators; 如果该运算符优先级低于栈顶元素,依次弹出栈顶元素并把它们存入到后缀表达式 postfix 中,直到遇到 ( 左括号或者优先级低于该运算符的栈顶元素时,保持栈顶元素不变,并将该运算符压栈到 operators
  • 扫描完毕中缀表达式后,将运算符栈 operators 中的其余元素依次弹出并追加到后缀表达式列表中。

2. 后缀表达式的求值

后缀表达式的求值过程中需要一个操作数栈 operands 来存储中间结果,可以用 Python 的列表来实现。 求值步骤如下:

  • 从左到右扫描后缀表达式
  • 如果是操作数,将操作数压栈
  • 如果是运算符,弹出两个操作数进行运算(注意顺序,先弹出是右操作数,后弹出的是左操作数)
  • 将运算结果压栈
  • 将后缀表达式扫描完毕
  • 最后操作数栈 operands 就剩一个元素,该元素为后缀表达式的结果

3. Python 代码实现

# 运算符元组
OPERATORS = ('+', '-', '*', '/', '(', ')')
# 优先级字典
PRIORITY = dict([
    ('+', 1),
    ('-', 1),
    ('*', 2),
    ('/', 2)
])


def pop_left_bracket(postfix, operators):
    """依次弹栈并追加到后缀表达式,直到遇到左括号为止。"""
    while operators:
        operator = operators.pop()
        if operator == '(':
            break
        else:
            postfix.append(operator)


def compare_and_pop(i, postfix, operators):
    """比较优先级并进行相应操作。"""
    if len(operators) == 0:
        operators.append(i)
        return
    while operators:
        operator = operators.pop()
        if operator == '(':
            operators += ['(', i]
            return
        elif PRIORITY[i] > PRIORITY[operator]:
            operators += [operator, i]
            return
        else:
            postfix.append(operator)
    operators.append(i)


def pop_rest(postfix, operators):
    """弹出所有剩余的运算符,追加到后缀表达式。"""
    while operators:
        postfix.append(operators.pop())


def valid_infix(infix):
    pass


def is_number(text):
    """判断字符串是否为整数或者浮点数。"""
    try:
        return int(text)
    except:
        try:
            return float(text)
        except:
            return None


def infix_to_postfix(infix):
    """将中缀表达式转换为后缀表达式。"""
    infix = infix.split()
    postfix = []
    operators = []

    # TODO jpch89: 检查中缀表达式合法性
    # if not valid_infix(infix):
    #     return None

    for i in infix:
        if is_number(i):
            postfix.append(i)
        elif i in OPERATORS:
            if i == '(':
                operators.append(i)
            elif i == ')':
                pop_left_bracket(postfix, operators)
            else:
                compare_and_pop(i, postfix, operators)

    pop_rest(postfix, operators)

    return postfix


def calc_postfix(postfix):
    """计算后缀表达式的值。"""
    operands = []

    for i in postfix:
        if is_number(i):
            operands.append(i)
        else:
            right = operands.pop()
            left = operands.pop()
            operands.append(str(eval(left + i + right)))

    try:
        return int(operands[0])
    except ValueError:
        return float(operands[0])


def main():
    infix = input('输入算式(请用空格分隔数字/运算符/括号):')

    postfix = infix_to_postfix(infix)
    if postfix is not None:
        print('后缀表达式为:%s' % ' '.join(postfix))
    else:
        print('不合法的算式!')
        exit()

    result = calc_postfix(postfix)
    print('计算结果为:%s' % result)


if __name__ == '__main__':
    main()


4. 待完善

  • 中缀表达式的合法性检测
  • 目前的代码强制使用空格分割运算符和操作数,需要去掉这个限制

完成于 2018.12.04