我实现了一个需要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
提前致谢
答案 0 :(得分:2)
原始的实施很好,很清楚。这是使用更多Pythonic功能的另一个:
使用py.test(< 3)
解析错误是孤立的,因为它们已经表明发生了什么
operator
模块直接用于许多双参数函数,如乘法
同样,单个参数数学函数如sin
/ cos
只需调用math
库
为方便起见,表达式可以指定为单个字符串,如"2 3 /"
计算器非常有趣,并且很好地介绍了编译和解析等很酷的主题。玩得开心!
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]
在导入后的前五行中,我将运算符和常量的本地名称作为优化,因此每次使用时都不必进行模块查找。允许e
和pi
等常量是一个额外的功能。
ops
词典是键到计算器的工作。
它将运算符的符号链接到元组,该元组包含运算符使用的参数数量和要调用的函数。由于这种数据结构,操作符不必由它们的参数数量来处理。
此外,我们会在元组中保存_ops
和_consts
dicts的键,因为我们会经常使用这些键。
行stack[-n:] = [op(*args)]
是计算器的核心。它
包含两个技巧。首先,它使用*
运算符进行参数解包。其次,它使用op
的结果替换堆栈上的多个值。
有意地,此函数不会捕获由输入数据中的错误引起的异常。