我正在用Python编写一个计算器(作为练习),有一点我想知道。
程序将输入拆分为数字和运算符列表。然后计算结果:
import operator
ops = {'+' : operator.add, # operators and corresponding functions
'-' : operator.sub,
'*' : operator.mul,
'/' : operator.truediv,
'%' : operator.mod}
precedence = [['*', '/', '%'], ['+', '-']] # order of precedence for operators
def evaluate(exp):
for oplist in precedence: # search for operators first in order of precedence
for op in exp: # then from left to right
if op in oplist:
index = exp.index(op)
result = ops[op](exp[index - 1], exp[index + 1])
# compute the result of the operation
exp[index - 1:index + 2] = [result]
# replace operation and operands with result
return exp[0]
# for example,
evaluate([2, '+', 3, '+', 4, '+', 5])
# should return 14
此函数按优先级递减的顺序查看算术运算符列表,然后从左到右查找,当找到这样的运算符时,它调用相邻列表元素(操作数)上的相应函数并替换运算符和列表中的操作数与操作结果。一旦执行了所有操作,列表将包含一个元素 - 计算结果。
但是,此功能的行为方式并不像预期的那样。问题(我认为)是这个函数在迭代它时修改列表(通过分配给切片)。我已经找到了解决这个问题的方法here(通过每次修改列表时重新启动内部for
循环),但是给出解决方案的人似乎认为通常应该有更好的如何实现这一目标。
我想知道是否有更好的方法来实现这个算法,避免了奇怪的“重启循环”。
感谢您的任何想法!
答案 0 :(得分:3)
如果使用while
循环
def evaluate(exp):
for oplist in precedence: # search for operators first in order of precedence
idx = 0
while idx < len(exp):
op = exp[idx]
if op in oplist:
result = ops[op](exp[idx - 1], exp[idx + 1])
exp[idx - 1:idx + 2] = [result]
idx -= 1 # move index back since list is shortened by 2
else:
idx += 1
return exp[0]
答案 1 :(得分:3)
我想我会采用不同的方式,并使用递归函数。弹出操作并将其替换为评估结果。
import operator
ops = {
'+' : operator.add,
'-' : operator.sub,
'*' : operator.mul,
'/' : operator.truediv,
'%' : operator.mod,
}
precedence = [
set(['*', '/', '%']),
set(['+', '-']),
]
def evaluate(expr):
# if len == 3 then just return result of expression
if len(expr) == 3:
l, op, r = expr
return ops[op](l, r)
else:
for op_list in precedence:
for op in expr:
if op in op_list:
# find index of first operation
idx = expr.index(op)-1
# pop off and evaluate first matching operation in expr
result = evaluate([expr.pop(idx) for i in range(3)])
# insert result back into expr
expr.insert(idx, result)
return evaluate(expr)
答案 2 :(得分:2)
我不确定“重启循环”是什么意思。在这种特殊情况下,在我看来,你应该简单地将一个函数重复应用于表达式,直到它被减少到一个值。这效率低于它可能的效果,但它的工作原理很明确。所以......
def find_op(tokens, oplist):
for i, token in enumerate(tokens):
if token in oplist:
return i
else:
return -1
def reduce_binary_infix(tokens, i, ops):
op = ops[tokens[i]]
tokens[i - 1: i + 2] = [op(tokens[i - 1], tokens[i + 1])]
return tokens
def evaluate(tokens, ops, precedence):
for prec in precedence:
index = find_op(tokens, prec)
while index >= 0:
tokens = reduce_binary_infix(tokens, index, ops)
index = find_op(tokens, prec)
return tokens
print evaluate([2, '+', 3, '+', 4, '+', 5], ops, precedence)
测试:
>>> print evaluate([2, '+', 3, '+', 4, '+', 5], ops, precedence)
[14]
通过不重复搜索整个字符串,可以提高效率。这可以通过在start_index
中设置find_op
参数,并让reduce_binary_infix
返回新的当前索引以及缩减列表来完成。
这也比你的更冗长,但我认为它有助于代码的可读性 - 更不用说它的可重用性了。