【问题标题】:Is there a better way to implement this?有没有更好的方法来实现这一点?
【发布时间】:2011-10-23 18:14:01
【问题描述】:

我正在用 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 循环),但是给出解决方案的人似乎认为通常应该有更好的方法来完成任何事情这是需要的。

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

感谢您的任何想法!

【问题讨论】:

标签: python implementation calculator


【解决方案1】:

如果您改用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]

【讨论】:

    【解决方案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]
    

    不重复搜索整个字符串可以提高效率。这可以通过在find_op 中使用start_index 参数并让reduce_binary_infix 返回一个新的当前索引以及缩减列表来完成。

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

    【讨论】:

      【解决方案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)
      

      【讨论】:

      • +1 以获得最容易理解的解决方案。除非效率不高,因为您对给定运算符执行两次相同运算符的整个表达式的线性搜索,当您执行 if op in expridx = expr.index(op)-1 时。我只是将整个单运算符搜索嵌套在 try-except 块中,因为如果 op 不在 expr 中,expr.index(op) 会引发错误。
      • 谢谢!我绝对是为了可读性而拍摄,但这是一个很好的观点:)
      • 这是一个很好的解决方案,但是对于像 2 - 3 + 4 这样的表达式来说,需要一组相等的优先级。如果它在 '-' 之前搜索 '+',那么 2 - 3 + 4 = 2 - 7 = -5,这是不正确的。当运算符具有相同的优先级时,它们必须作为一个组进行搜索。
      • @discipulus 当然,我认为这无关紧要是愚蠢的。我将像原始问题一样将搜索运算符的答案作为一个组调整。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-15
      • 2013-10-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多