RPN评估者优化而不会失去可读性

时间:2016-05-09 23:13:04

标签: python rpn

我实现了一个需要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

提前致谢

2 个答案:

答案 0 :(得分:2)

原始的实施很好,很清楚。这是使用更多Pythonic功能的另一个:

  • 使用py.test(< 3)

  • 进行测试
  • 解析错误是孤立的,因为它们已经表明发生了什么

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

  • 同样,单个参数数学函数如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'

答案 1 :(得分:1)

这是我的版本:

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 dicts的键,因为我们会经常使用这些键。

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

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