Python中itertools.combinations的算法

时间:2017-01-17 10:30:29

标签: python algorithm

我正在解决涉及组合的编程难题。这让我有了一个很棒的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

这次演习非常简洁,我怀疑这是所有魔法发生的地方。请给我一个提示,以便我能弄清楚。

3 个答案:

答案 0 :(得分:3)

这个for循环做了一件简单的事情:它检查算法是否应该终止

算法从第一个r项开始,并逐渐增加,直到它到达可迭代中的最后r个项目为[Sn-r+1 ... Sn-1, Sn](如果我们让S为迭代)。

现在,该算法会扫描索引中的每个项目,并确保它们仍然可以去哪里 - 因此它会验证i indice 索引{{1}上一段是(我们在这里忽略1因为列表是从0开始的)。

如果所有这些索引都等于最后n - r + i个位置 - 那么它会进入r,提交else并终止算法。

我们可以使用

创建相同的功能
return

但是这个“混乱”(使用if indices == list(range(n-r, n)): return reverse)的主要原因是,不匹配的第一个索引保存在break内并被使用为算法的下一级增加该指数,并负责重新设置其余部分。

您可以将i替换为

来检查
yield

答案 1 :(得分:3)

循环有两个目的:

  1. 如果已到达最后一个索引列表,则终止
  2. 确定索引列表中可以合法增加的最右侧位置。这个位置是重置右边所有权利的起点。
  3. 让我们假设您有一个超过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大于其前身。

答案 2 :(得分:1)

Source code有一些有关正在发生的事情的其他信息。

yeild循环之前的while语句返回一个简单的元素组合(只是rA的第一个(A[0], ..., A[r-1])元素并准备{ {1}}用于未来的工作。 我们假设我们有indicesA='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]的{​​{1}}元素:

i

因此,我们得到的索引组合indices指向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]

指数逐步增加:

步骤指数

  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) ...