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]
在导入后的前五行中,我为运算符和常量创建了本地名称作为优化,这样我们就不必在每次使用时都进行模块查找。允许 e
和 pi
等常量是一项额外功能。
ops
字典是计算器工作的关键。
它将运算符的符号链接到一个元组,该元组包含运算符使用的参数数量和要调用的函数。由于这种数据结构,运算符不必根据参数的数量进行特殊处理。
此外,我们将 _ops
和 _consts
指令的键保存在元组中,因为我们会经常使用它们。
stack[-n:] = [op(*args)]
行是计算器的核心。它
包含两个技巧。首先,它使用 *
运算符进行参数解包。其次,它用 op
.
的结果替换堆栈上的多个值
有意地,此函数不捕获由输入数据中的错误引起的异常。
我正在实现一个需要 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]
在导入后的前五行中,我为运算符和常量创建了本地名称作为优化,这样我们就不必在每次使用时都进行模块查找。允许 e
和 pi
等常量是一项额外功能。
ops
字典是计算器工作的关键。
它将运算符的符号链接到一个元组,该元组包含运算符使用的参数数量和要调用的函数。由于这种数据结构,运算符不必根据参数的数量进行特殊处理。
此外,我们将 _ops
和 _consts
指令的键保存在元组中,因为我们会经常使用它们。
stack[-n:] = [op(*args)]
行是计算器的核心。它
包含两个技巧。首先,它使用 *
运算符进行参数解包。其次,它用 op
.
有意地,此函数不捕获由输入数据中的错误引起的异常。