避免嵌套for循环python

时间:2017-04-02 20:26:35

标签: python loops for-loop nested

我有一个函数,它接受表达式并用我用作输入的值的所有排列替换变量。这是我测试和工作的代码,但是在查看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']))

4 个答案:

答案 0 :(得分:3)

前言

嵌套循环本身并不是一件坏事。它们只是很糟糕,如果有问题,已经找到了更好的算法(在输入大小方面的效率方面有好有坏)。例如,对整数列表进行排序就是一个问题。

分析问题

大小

在你的情况下,你有三个列表,全部大小4.这使得4 * 4 * 4 = 64种可能的组合,如果a总是在b之前,b在b之前。所以你需要至少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']))

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

Itertools

注意到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']))

答案 1 :(得分:3)

这里有一些想法:

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

  2. 使用列表理解,它们比附加的正常for循环快一点

  3. 代替.replace,使用.format,它只需一步即可完成替换

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

  5. 所有这些,我到达了这个

    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。

    的版本相同

答案 2 :(得分:1)

因此,您当前的结构解决了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构造,而不是在e中重复_replace_variables两次。这个函数看起来像:

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)

答案 3 :(得分:0)

嵌套循环的“问题”基本上只是级别的数量是硬编码的。您为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"])只能在顶部创建一次,甚至可以传递给函数。

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