我正在解决涉及组合的编程难题。这让我有了一个很棒的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
这次演习非常简洁,我怀疑这是所有魔法发生的地方。请给我一个提示,以便我能弄清楚。
答案 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)
循环有两个目的:
让我们假设您有一个超过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
语句返回一个简单的元素组合(只是r
,A
的第一个(A[0], ..., A[r-1])
元素并准备{ {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]
的{{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]
指数逐步增加:
步骤指数