2015-02-07 47 views
60

在固體力學,我經常使用Python和編寫的代碼,如下所示:如何使Python for循環金字塔更簡潔?

for i in range(3): 
    for j in range(3): 
     for k in range(3): 
      for l in range(3): 
       # do stuff 

我這樣做真的很經常,我開始懷疑是否有這樣做的更簡潔的方式。當前代碼的缺點是:如果我符合PEP8,那麼我不能超過每行79個字符的限制,並且沒有太多剩餘空間,特別是如果這又是一個類的函數。

+0

你只是遍歷範圍?然後有一個更短(雖然不一定更可讀)的方式。 – L3viathan 2015-02-07 13:05:48

+5

如果算法是O(n^4),那麼它是O(n^4)。沒辦法。爲了避開79個字符的限制,考慮將它們分解爲函數。這對可讀性和可測試性都會產生奇蹟。 – SuperSaiyan 2015-02-07 13:06:55

+5

呃...深度嵌套循環不是一種很好的編程方式...所以我認爲你應該更多地關注避免深度嵌套循環而不是PEP8。 – 2015-02-07 13:07:23

回答

116

根據你想要做什麼,你可以使用itertools模塊,以儘量減少for環(或zip)。在這種情況下itertools.product將創建您用4個循環做了什麼:

>>> list(product(range(3),repeat=4)) 
[(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 1, 0), (0, 0, 1, 1), 
(0, 0, 1, 2), (0, 0, 2, 0), (0, 0, 2, 1), (0, 0, 2, 2), (0, 1, 0, 0), 
(0, 1, 0, 1), (0, 1, 0, 2), (0, 1, 1, 0), (0, 1, 1, 1), (0, 1, 1, 2), 
(0, 1, 2, 0), (0, 1, 2, 1), (0, 1, 2, 2), (0, 2, 0, 0), (0, 2, 0, 1), 
(0, 2, 0, 2), (0, 2, 1, 0), (0, 2, 1, 1), (0, 2, 1, 2), (0, 2, 2, 0), 
(0, 2, 2, 1), (0, 2, 2, 2), (1, 0, 0, 0), (1, 0, 0, 1), (1, 0, 0, 2), 
(1, 0, 1, 0), (1, 0, 1, 1), (1, 0, 1, 2), (1, 0, 2, 0), (1, 0, 2, 1), 
(1, 0, 2, 2), (1, 1, 0, 0), (1, 1, 0, 1), (1, 1, 0, 2), (1, 1, 1, 0), 
(1, 1, 1, 1), (1, 1, 1, 2), (1, 1, 2, 0), (1, 1, 2, 1), (1, 1, 2, 2), 
(1, 2, 0, 0), (1, 2, 0, 1), (1, 2, 0, 2), (1, 2, 1, 0), (1, 2, 1, 1), 
(1, 2, 1, 2), (1, 2, 2, 0), (1, 2, 2, 1), (1, 2, 2, 2), (2, 0, 0, 0), 
(2, 0, 0, 1), (2, 0, 0, 2), (2, 0, 1, 0), (2, 0, 1, 1), (2, 0, 1, 2), 
(2, 0, 2, 0), (2, 0, 2, 1), (2, 0, 2, 2), (2, 1, 0, 0), (2, 1, 0, 1), 
(2, 1, 0, 2), (2, 1, 1, 0), (2, 1, 1, 1), (2, 1, 1, 2), (2, 1, 2, 0), 
(2, 1, 2, 1), (2, 1, 2, 2), (2, 2, 0, 0), (2, 2, 0, 1), (2, 2, 0, 2), 
(2, 2, 1, 0), (2, 2, 1, 1), (2, 2, 1, 2), (2, 2, 2, 0), (2, 2, 2, 1), 
(2, 2, 2, 2)] 

而在你的代碼,你可以這樣做:

for i,j,k,l in product(range(3),repeat=4): 
    #do stuff 

此功能相當於下面的代碼,但實際執行情況並不BUI LD了中間結果在存儲器中:

def product(*args, **kwds): 
    # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy 
    # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 
    pools = map(tuple, args) * kwds.get('repeat', 1) 
    result = [[]] 
    for pool in pools: 
     result = [x+[y] for x in result for y in pool] 
    for prod in result: 
     yield tuple(prod) 

編輯:作爲@ PeterE在評論說product()可用於即使範圍具有不同的長度:

product(range(3),range(4),['a','b','c'] ,some_other_iterable) 
+7

恕我直言,這是要走的路。至少如果循環與OP中提出的一樣基本。這個方案也可以處理不同長度的範圍(實際上它可以處理任意迭代):'product(range(3),range(4),['a','b','c'],some_other_iterable)'。如果嵌套循環比這更復雜(例如包含一些if/else邏輯),那麼定義on生成器函數是次佳選擇。在這裏,我比較喜歡@ SergeBallesta的變體,因爲它簡單易懂。 – PeterE 2015-02-07 16:33:08

+0

@PeterE我認爲你可以在產品(範圍(3),重複= 4)中找到最簡單的'for i,j,k,l''也可以添加'product'的等價函數來回答你可以檢查它出去。 – Kasramvd 2015-02-07 17:52:39

+0

事後看來,我的評論可能會被誤解爲評論家。這不是我的意圖。我想表達的是,我發現你的解決方案對於給定的問題是最好的。我想提一下,即使範圍有不同的長度,也可以使用'product()'。 – PeterE 2015-02-07 21:51:44

7

這相當於:

for c in range(3**4): 
    i = c // 3**3 % 3 
    j = c // 3**2 % 3 
    k = c // 3**1 % 3 
    l = c // 3**0 % 3 
    print(i,j,k,l) 

如果你一直在做這個,考慮使用一個基因它RAL發生器:

def nestedLoop(n, l): 
    return ((tuple((c//l**x%l for x in range(n-1,-1,-1)))) for c in range(l**n)) 

for (a,b,c,d) in nestedLoop(4,3): 
    print(a,b,c,d) 
+0

'c // 1'?無論如何,如果你至少已經展示瞭如何將這個特定案例變成一個發電機,我可能會投票贊成。 – martineau 2015-02-07 14:42:23

+0

爲了更好的理解,我包含了'c // 1'(和第一個模)。 – L3viathan 2015-02-07 14:52:42

+1

在這種情況下,出於說明的目的,您可能應該使用'c // 3 ** 3%3','c // 3 ** 2%3'等.-)' – martineau 2015-02-07 15:07:23

13

這不會是更簡潔,因爲它會花費你一臺發電機的功能,但至少你不會PEP8困擾:

def tup4(n): 
    for i in range(n): 
     for j in range(n): 
      for k in range(n): 
       for l in range(n): 
        yield (i, j, k, l) 

for (i, j, k, l) in tup4(3): 
    # do your stuff 

(在python 2.X應該在生成函數中使用xrange代替range

編輯:

以上方法應該是罰款時,金字塔的深度硝酸鉀WN。但你也可以做一個通用發生器方式,無需任何外部模塊:

def tup(n, m): 
    """ Generate all different tuples of size n consisting of integers < m """ 
    l = [ 0 for i in range(n)] 
    def step(i): 
     if i == n : raise StopIteration() 
     l[i] += 1 
     if l[i] == m: 
      l[i] = 0 
      step(i+ 1) 
    while True: 
     yield tuple(l) 
     step(0) 

for (l, k, j, i) in tup(4, 3): 
    # do your stuff 

(我以前(l, k, j, i)因爲在上述發電機,第一指數變化第一)

15

使用itertools.product是一個很好的想法。這是一個更一般的方法,將支持不同大小的範圍。

from itertools import product 

def product_of_ranges(*ns): 
    for t in product(*map(range, ns)): 
     yield t 

for i, j, k in product_of_ranges(4, 2, 3): 
    # do stuff