【问题标题】:avoiding nested for loops python避免嵌套for循环python
【发布时间】:2017-08-27 15:26:05
【问题描述】:

我有一个函数,它接受表达式并用我用作输入的值的所有排列替换变量。这是我已经测试过并且可以工作的代码,但是在查看了 SO 之后,人们说嵌套 for 循环是一个坏主意,但是我不确定如何使它更有效。有人可以帮忙吗?谢谢。

def replaceVar(expression):

    eval_list = list()

    a = [1, 8, 12, 13]
    b = [1, 2, 3, 4]
    c = [5, 9, 2, 7]

    for i in expression:
        first_eval = [i.replace("a", str(j)) for j in a]
        tmp = list()
        for k in first_eval:
            snd_eval = [k.replace("b", str(l)) for l in b]
            tmp2 = list()
            for m in snd_eval:
                trd_eval = [m.replace("c", str(n)) for n in c]
                tmp2.append(trd_eval)
            tmp.append(tmp2)
        eval_list.append(tmp)
    print(eval_list)
    return eval_list

print(replaceVar(['b-16+(c-(a+11))', 'a-(c-5)+a-b-10']))

【问题讨论】:

  • itertools.product 在这里可能有用。
  • 我看过 itertools 但不确定如何实现
  • @carl saptarshi:以下任何答案对您有帮助吗?请记住将帮助您的答案标记为已接受的答案,或者以其他方式解释为什么它们都没有帮助您。
  • 是的,R. Q. 确实帮助了我!并解释了该怎么做,谢谢:)
  • 您能否将其标记为已接受的答案,然后请:-)

标签: python loops for-loop nested


【解决方案1】:

前言

嵌套循环本身并不是一件坏事。它们只有在用于解决已找到更好算法的问题时才是坏的(就输入大小的效率而言,无论好坏)。例如,整数列表的排序就是这样一个问题。

分析问题

尺寸

在你上面的例子中,你有三个列表,大小都是 4。如果 a 总是在 b 之前,b 在 c 之前,这使得它们有 4 * 4 * 4 = 64 种可能的组合。所以你至少需要 64 次迭代!

你的方法

在您的方法中,我们对 a 的每个可能值进行 4 次迭代,对 b 的每个可能值进行 4 次迭代,对 c 也是如此。所以我们总共有 4 * 4 * 4 = 64 次迭代。所以实际上你的解决方案非常好! 由于没有更快的方式来聆听所有组合,因此您的方式也是最好的。

风格

关于风格可以说你可以通过更好的变量名和结合一些 for 循环来改进你的代码。例如。像这样:

def replaceVar(expressions):
    """
    Takes a list of expressions and returns a list of expressions with
    evaluated variables.
    """
    evaluatedExpressions = list()

    valuesOfA = [1, 8, 12, 13]
    valuesOfB = [1, 2, 3, 4]
    valuesOfC = [5, 9, 2, 7]

    for expression in expressions:
        for valueOfA in valuesOfA:
            for valueOfB in valuesOfB:
                for valueOfC in valuesOfC:
                    newExpression = expression.\
                                    replace('a', str(valueOfA)).\
                                    replace('b', str(valueOfB)).\
                                    replace('c', str(valueOfC))
                    evaluatedExpressions.append(newExpression)

    print(evaluatedExpressions)
    return evaluatedExpressions

print(replaceVar(['b-16+(c-(a+11))', 'a-(c-5)+a-b-10']))

但请注意,迭代次数保持不变!

迭代工具

正如Kevin 所注意到的,您也可以使用itertools 来生成笛卡尔积。在内部,它将与您对组合 for 循环所做的相同:

import itertools

def replaceVar(expressions):
    """
    Takes a list of expressions and returns a list of expressions with
    evaluated variables.
    """
    evaluatedExpressions = list()

    valuesOfA = [1, 8, 12, 13]
    valuesOfB = [1, 2, 3, 4]
    valuesOfC = [5, 9, 2, 7]

    for expression in expressions:
        for values in itertools.product(valuesOfA, valuesOfB, valuesOfC):
            valueOfA = values[0]
            valueOfB = values[1]
            valueOfC = values[2]
            newExpression = expression.\
                            replace('a', str(valueOfA)).\
                            replace('b', str(valueOfB)).\
                            replace('c', str(valueOfC))
            evaluatedExpressions.append(newExpression)

    print(evaluatedExpressions)
    return evaluatedExpressions

print(replaceVar(['b-16+(c-(a+11))', 'a-(c-5)+a-b-10']))

【讨论】:

  • 我会使用元组解包来删除三行代码:for a, b, c in itertools.product(...)
  • @Jared Goguen:确实如此。但我想保留与上面相同的变量 - 以查看相关性 - 并且随着元组解包,行变得很长。
【解决方案2】:

这里有一些想法:

  1. 由于您的列表 a、b 和 c 是硬编码的,因此将它们硬编码为字符串,因此您不必在每一步都将每个元素都转换为字符串

  2. 使用列表推导,它们比普通的带附加的 for 循环快一点

  3. 使用.format代替.replace,它会一步完成所有替换

  4. 使用 itertools.product 组合 a、b 和 c

有了这一切,我到达了这个

import itertools

def replaceVar(expression):

    a = ['1', '8', '12', '13' ]
    b = ['1', '2', '3', '4' ]
    c = ['5', '9', '2', '7' ]
    expression = [exp.replace('a','{0}').replace('b','{1}').replace('c','{2}') 
                  for exp in expression] #prepare the expresion so they can be used with format

    return [ exp.format(*arg) for exp in expression  for arg in itertools.product(a,b,c) ]

速度增益是微不足道的,但在我的机器上它从 148 毫秒变为 125 毫秒

功能与R.Q.

版本相同

【讨论】:

    【解决方案3】:

    嵌套循环的“问题”基本上只是层数是硬编码的。您为 3 个变量编写了嵌套。如果你只有2个怎么办?如果它跳到5怎么办?然后你需要对代码进行非平凡的手术。这就是为什么推荐itertools.product()

    与此相关的是,到目前为止,所有建议都硬编码了replace() 调用的数量。相同的“问题”:如果您没有恰好 3 个变量,则必须修改替换代码。

    与其这样做,不如考虑一种更清洁的方式来进行替换。例如,假设您的输入字符串是:

    s = '{b}-16+({c}-({a}+11))'
    

    代替:

    'b-16+(c-(a+11))'
    

    也就是说,要替换的变量用大括号括起来。然后 Python 可以“一次”为您完成所有替换:

    >>> s.format(a=333, b=444, c=555)
    '444-16+(555-(333+11))'
    

    这也对名称和名称数量进行了硬编码,但同样的事情可以用 dict 来完成:

    >>> d = dict(zip(["a", "b", "c"], (333, 444, 555)))
    >>> s.format(**d)
    '444-16+(555-(333+11))'
    

    现在,format() 调用中没有硬编码变量的数量或名称。

    值元组 ((333, 444, 555)) 正是 itertools.product() 返回的那种东西。变量名列表 (["a", "b", "c"]) 可以在顶部只创建一次,甚至可以传递给函数。

    您只需要一些代码来转换输入表达式以将变量名称括在花括号中。

    【讨论】:

      【解决方案4】:

      因此,您当前的结构解决了 itertools.product 解决方案无法解决的低效率问题之一。您的代码正在保存中间替换的表达式并重用它们,而不是用每个 itertools.product 元组重做这些替换。这很好,我认为您当前的代码很有效。

      但是,它很脆弱,只有在恰好替换三个变量时才有效。动态规划方法可以解决这个问题。为此,我将稍微改变输入参数。该函数将使用两个输入:

      expressions - 要替换​​的表达式

      replacement_map - 提供替换每个变量的值的字典

      动态规划函数如下:

      def replace_variable(expressions, replacement_map):
          return [list(_replace_variable([e], replacement_map)) for e in expressions]
      
      def _replace_variable(expressions, replacement_map):
          if not replacement_map:
              for e in expressions:
                  yield e
          else:
              map_copy = replacement_map.copy()
              key, value_list = map_copy.popitem()
              for value in value_list:
                  substituted = [e.replace(key, value) for e in expressions]
                  for e in _replace_variable(substituted, map_copy):
                      yield e
      

      附示例用法:

      expressions = ['a+b', 'a-b']
      
      replacement_map = {
          'a': ['1', '2'],
          'b': ['3', '4']
      }
      
      print replace_variable(expressions, replacement_map)
      # [['1+3', '1+4', '2+3', '2+4'], ['1-3', '1-4', '2-3', '2-4']]
      

      请注意,如果您使用的是 Python 3.X,则可以使用 yield from iterator 构造,而不是在 _replace_variables 中重复 e 两次。这个函数看起来像:

      def _replace_variable(expressions, replacement_map):
          if not replacement_map:
              yield from expressions
      
          else:
              map_copy = replacement_map.copy()
              key, value_list = map_copy.popitem()
              for value in value_list:
                  substituted = [e.replace(key, value) for e in expressions]
                  yield from _replace_variable(substituted, map_copy)
      

      【讨论】:

        猜你喜欢
        • 2017-11-11
        • 2012-06-25
        • 1970-01-01
        • 1970-01-01
        • 2020-05-21
        • 2019-12-26
        • 2017-09-20
        • 2023-03-15
        • 1970-01-01
        相关资源
        最近更新 更多