有没有更好的方法来实现这个?

时间:2011-08-10 03:59:15

标签: python implementation calculator

我正在用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循环),但是给出解决方案的人似乎认为通常应该有更好的如何实现这一目标。

我想知道是否有更好的方法来实现这个算法,避免了奇怪的“重启循环”。

感谢您的任何想法!

3 个答案:

答案 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返回新的当前索引以及缩减列表来完成。

这也比你的更冗长,但我认为它有助于代码的可读性 - 更不用说它的可重用性了。