簡體   English   中英

Python中itertools.combinations的算法

[英]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

但是這個“混亂”(使用reversebreak )的主要原因是從不匹配的結尾的第一個索引保存在i並且用於算法的下一級別,該算法遞增該索引並負責重新設定其余部分。


您可以通過替換yield s來檢查這一點

print('Combination: {}  Indices: {}'.format(tuple(pool[i] for i in indices), indices))

該循環有兩個目的:

  1. 如果已到達最后一個索引列表,則終止
  2. 確定索引列表中可以合法增加的最右側位置。 然后,該位置是重置右側所有權利的起點。

假設您有一個超過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]

接下來,以下代碼遞增indicesi個元素:

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;

指數逐步增加:

步指數

  1. (0,1,2)
  2. (0,1,3)
  3. (0,1,4)
  4. (0,2,3)
  5. (0,2,4)
  6. (0,3,4)
  7. (1,2,3)......

暫無
暫無

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

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