[英]Algorithm for itertools.combinations in Python
我正在解決涉及組合的編程難題。 它讓我有了一個很棒的itertools.combinations
功能,我想知道它是如何工作的。 文檔說該算法大致相當於以下內容:
def combinations(iterable, r):
# combinations('ABCD', 2) --> AB AC AD BC BD CD
# combinations(range(4), 3) --> 012 013 023 123
pool = tuple(iterable)
n = len(pool)
if r > n:
return
indices = list(range(r))
yield tuple(pool[i] for i in indices)
while True:
for i in reversed(range(r)):
if indices[i] != i + n - r:
break
else:
return
indices[i] += 1
for j in range(i+1, r):
indices[j] = indices[j-1] + 1
yield tuple(pool[i] for i in indices)
我明白了:我們從最明顯的組合( r
一個連續的元素)開始。 然后我們更改一個(最后一個)項目以獲得每個后續組合。
我掙扎的事情是有條件內for
循環。
for i in reversed(range(r)):
if indices[i] != i + n - r:
break
這次演習非常簡潔,我懷疑這是所有魔法發生的地方。 請給我一個提示,這樣我就可以搞清楚。
這個for循環做了一件簡單的事情: 它檢查算法是否應該終止 。
該算法從前r
項開始並逐漸增加,直到達到迭代中的最后r
項,即[Sn-r+1 ... Sn-1, Sn]
(如果我們讓S
為可迭代的)。
現在,算法掃描索引中的每個項目,並確保它們仍然有去處 - 所以它驗證第i
個指標不是索引n - r + i
,前一段是(我們忽略1)這里因為列表是基於0的)。
如果所有這些索引都等於最后的r
位置 - 那么它進入else
,提交return
並終止算法。
我們可以使用創建相同的功能
if indices == list(range(n-r, n)): return
但是這個“混亂”(使用reverse
和break
)的主要原因是從不匹配的結尾的第一個索引保存在i
並且用於算法的下一級別,該算法遞增該索引並負責重新設定其余部分。
您可以通過替換yield
s來檢查這一點
print('Combination: {} Indices: {}'.format(tuple(pool[i] for i in indices), indices))
該循環有兩個目的:
假設您有一個超過5個元素的可迭代元素,並且需要長度為3的組合。您實際需要的是生成索引列表。 上述算法的多汁部分從當前的算法生成下一個這樣的索引列表:
# obvious
index-pool: [0,1,2,3,4]
first index-list: [0,1,2]
[0,1,3]
...
[1,3,4]
last index-list: [2,3,4]
i + n - r
是索引列表中索引i
的最大值:
index 0: i + n - r = 0 + 5 - 3 = 2
index 1: i + n - r = 1 + 5 - 3 = 3
index 2: i + n - r = 2 + 5 - 3 = 4
# compare last index-list above
=>
for i in reversed(range(r)): if indices[i] != i + n - r: break else: break
它向后循環通過當前索引列表,並在不保持其最大索引值的第一個位置停止。 如果所有頭寸都保持其最大索引值,則沒有其他索引列表,因此return
。
在[0,1,4]
的一般情況下,可以驗證下一個列表應該是[0,2,3]
。 循環停在位置1
,即后續代碼
indices[i] += 1
增加indeces[i]
( 1 -> 2
)的值。 最后
for j in range(i+1, r): indices[j] = indices[j-1] + 1
將所有位置> i
重置為最小的合法索引值,每個值比其前一個大1
。
源代碼有一些關於發生了什么的額外信息。
while
循環之前的yeild
語句返回一個簡單的元素組合(它只是A
第一個r
元素, (A[0], ..., A[r-1])
)並為將來的工作准備indices
。 假設我們有A='ABCDE'
且r=3
。 然后,在第一步之后, indices
值為[0, 1, 2]
,其指向('A', 'B', 'C')
。
讓我們看看有問題的循環的源代碼:
2160 /* Scan indices right-to-left until finding one that is not
2161 at its maximum (i + n - r). */
2162 for (i=r-1 ; i >= 0 && indices[i] == i+n-r ; i--)
2163 ;
此循環搜索尚未達到其最大值的indices
的最右側元素。 在第一個yield
語句之后, indices
的值是[0, 1, 2]
。 因此, for
循環終止於indices[2]
。
接下來,以下代碼遞增indices
第i
個元素:
2170 /* Increment the current index which we know is not at its
2171 maximum. Then move back to the right setting each index
2172 to its lowest possible value (one higher than the index
2173 to its left -- this maintains the sort order invariant). */
2174 indices[i]++;
結果,我們得到索引組合[0, 1, 3]
,它指向('A', 'B', 'D')
。
然后我們回滾后續指數,如果它們太大:
2175 for (j=i+1 ; j<r ; j++)
2176 indices[j] = indices[j-1] + 1;
指數逐步增加:
步指數
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.