簡體   English   中英

避免嵌套for循環python

[英]avoiding nested for loops python

我有一個函數,它接受表達式並用我用作輸入的值的所有排列替換變量。 這是我測試和工作的代碼,但是在查看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.這使得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

正如凱文注意到的那樣,您也可以使用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. 如你的列表a,b和c是硬編碼的,將它們編碼為字符串,因此您不必在每一步都將每個元素轉換為字符串

  2. 使用列表理解,它們比帶有追加的普通for循環快一點

  3. 而不是.replace,使用.format ,它只需一步即可完成所有替換

  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

功能與RQ版本相同

嵌套循環的“問題”基本上只是級別的數量是硬編碼的。 您為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"] )可以在頂部創建一次,甚至可以傳遞給函數。

您只需要一些代碼來轉換輸入表達式,將變量名稱括在花括號中。

因此,您當前的結構解決了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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM