RPN Evaluator优化而不失可读性

RPN Evaluator optimization without losing readability

我正在实现一个需要 RPN 计算器功能的程序,我有下面的那个,但是我是新手 python 我想知道我是否可以在不失去可读性的情况下优化它。

使用字典等找到了一些解决方案,但我在 'pythonesque' 部分迷路了,这些列表对我来说仍然有些神秘...

我的函数是:

def parseRPN(expression):
    """Parses and calculates the result fo an RPN expression
        takes a list in the form of ['2','2','*']
        returns 4
    """
    try:
        stack = []
        for val in expression:
            if val in ['-', '+', '*', '/']:
                op1 = stack.pop()
                op2 = stack.pop()
                if val=='-': result = op2 - op1
                if val=='+': result = op2 + op1
                if val=='*': result = op2 * op1
                if val=='/':
                  if op1==0:
                      result=1
                  else:
                      result = op2 / op1
                stack.append(result)
            elif val in ['sin','cos']:
                op1 =stack.pop()
                if val=='sin': result = sin(op1)
                if val == 'cos': result = cos(op1)
                stack.append(result)
            else:
                stack.append(float(val))
        return stack.pop()
    except:
        print('error parse RPN fn:parse_rpn :' + str(expression))
        return 10*10**10

提前致谢

原来的实现很好,也很清楚。这是另一个使用更多 Pythonic 特性的:

  • 测试使用 py.test (<3)

  • 不理会解析错误,因为它们已经表明发生了什么

  • operator 模块直接用于许多双参数函数,例如 multiply

  • 同样,像 sin/cos 这样的单参数数学函数只需调用 math

  • 为方便起见,表达式可以指定为单个字符串,如"2 3 /"

计算器很有趣,是对编译和解析等很酷的主题的很好的介绍。玩得开心!

RPN 计算器

import math
import operator

import pytest


ERROR_VALUE = -1.


def safe_divide(darg1, darg2):
    try:
        return darg1/darg2
    except ZeroDivisionError:
        return ERROR_VALUE

def parseRPN(expression):
    """Parses and calculates the result of a RPN expression
        takes a list in the form of ['2','2','*']
        returns 4
    """
    # allow simple string: "2 3 /"
    if isinstance(expression, basestring):
        expression = expression.split()

    function_twoargs = {
    '*': operator.mul,
    '/': safe_divide,
    '+': operator.add,
    '-': operator.sub,
    }
    function_onearg = {
    'sin': math.sin,
    'cos': math.cos,
    }
    stack = []
    for val in expression:
        result = None
        if val in function_twoargs:
            arg2 = stack.pop()
            arg1 = stack.pop()
            result = function_twoargs[val](arg1, arg2)
        elif val in function_onearg:
            arg = stack.pop()
            result = function_onearg[val](arg)
        else:
            result = float(val)
        stack.append(result)
    return stack.pop()


def test_happy_paths():
    assert parseRPN([2, 3, '*']) == 6.
    assert parseRPN('0 sin') == 0.
    assert parseRPN([2, 3, '/']) == 2./3

def test_safe_divide():
    assert parseRPN([2, 0, '/']) == ERROR_VALUE

def test_parse_error():
    with pytest.raises(ValueError) as excinfo:
        parseRPN('gin')
    assert str(excinfo.value) == 'could not convert string to float: gin'

这是我的版本:

import operator
import math

_add, _sub, _mul = operator.add, operator.sub, operator.mul
_truediv, _pow, _sqrt = operator.truediv, operator.pow, math.sqrt
_sin, _cos, _tan, _radians = math.sin, math.cos, math.tan, math.radians
_asin, _acos, _atan = math.asin, math.acos, math.atan
_degrees, _log, _log10 = math.degrees, math.log, math.log10
_e, _pi = math.e, math.pi
_ops = {'+': (2, _add), '-': (2, _sub), '*': (2, _mul), '/': (2, _truediv),
        '**': (2, _pow), 'sin': (1, _sin), 'cos': (1, _cos), 'tan': (1, _tan),
        'asin': (1, _asin), 'acos': (1, _acos), 'atan': (1, _atan),
        'sqrt': (1, _sqrt), 'rad': (1, _radians), 'deg': (1, _degrees),
        'ln': (1, _log), 'log': (1, _log10)}
_okeys = tuple(_ops.keys())
_consts = {'e': _e, 'pi': _pi}
_ckeys = tuple(_consts.keys())


def postfix(expression):
    """
    Evaluate a postfix expression.

    Arguments:
        expression: The expression to evaluate. Should be a string or a
                    sequence of strings. In a string numbers and operators
                    should be separated by whitespace

    Returns:
        The result of the expression.
    """
    if isinstance(expression, str):
        expression = expression.split()
    stack = []
    for val in expression:
        if val in _okeys:
            n, op = _ops[val]
            if n > len(stack):
                raise ValueError('not enough data on the stack')
            args = stack[-n:]
            stack[-n:] = [op(*args)]
        elif val in _ckeys:
            stack.append(_consts[val])
        else:
            stack.append(float(val))
    return stack[-1]

在导入后的前五行中,我为运算符和常量创建了本地名称作为优化,这样我们就不必在每次使用时都进行模块查找。允许 epi 等常量是一项额外功能。

ops字典是计算器工作的关键。 它将运算符的符号链接到一个元组,该元组包含运算符使用的参数数量和要调用的函数。由于这种数据结构,运算符不必根据参数的数量进行特殊处理。

此外,我们将 _ops_consts 指令的键保存在元组中,因为我们会经常使用它们。

stack[-n:] = [op(*args)]行是计算器的核心。它 包含两个技巧。首先,它使用 * 运算符进行参数解包。其次,它用 op.

的结果替换堆栈上的多个值

有意地,此函数不捕获由输入数据中的错误引起的异常。