您在评论中提到这里的代码对您来说是不透明的。但这可能是实现combinations
您所针对的功能的最佳方式,并且值得理解,因此我将尝试详细解释它。
基本思想是给定一个序列和许多可供选择的项目,我们可以将每个组合表示为给定序列的索引序列。例如,假设我们有一个 list ['a', 'b', 'c', 'd', 'e']
,我们想从该列表中生成两个值的所有组合。
我们的第一个组合看起来像这样......
['a', 'b', 'c', 'd', 'e']
^ ^
...并由索引列表表示[0, 1]
。我们的下一个组合如下所示:
['a', 'b', 'c', 'd', 'e']
^ ^
并由索引列表表示[0, 2]
。
我们继续向前移动第二个插入符号,将第一个插入符号保持在原位,直到第二个插入符号到达末尾。然后我们将第一个插入符号移回 index1
并通过将第二个插入符号移回 index 来“重置”该过程2
。
['a', 'b', 'c', 'd', 'e']
^ ^
然后我们重复这个过程,将第二个插入符号向前移动直到它到达末尾,然后将第一个插入符号向前移动一个并重置第二个。
现在我们必须弄清楚如何通过操纵索引列表来做到这一点。事实证明,这很简单。最终组合将如下所示:
['a', 'b', 'c', 'd', 'e']
^ ^
并且它的索引表示将是[3, 4]
。这些是索引的最大可能值,等于i + n - r
,其中i
是列表中的位置,n
是值的数量(5
在这种情况下),并且r
是选择的数量(2
在这种情况下)。因此,一旦特定索引达到此值,它就不能再高了,需要“重置”。
因此,考虑到这一点,下面是对代码的逐步分析:
def combinations(iterable, r):
pool = tuple(iterable)
n = len(pool)
首先,给定基于上述示例的输入,pool
将是我们上面转换为元组的字符列表,并且n
只是池中的项目数。
if r > n:
return
我们不能在不替换的情况下n
从项目列表中选择多个项目,因此在这种情况下我们只需返回。n
indices = range(r)
现在我们有了索引,初始化为第一个组合 ( [0, 1]
)。所以我们产生它:
yield tuple(pool[i] for i in indices)
然后我们使用无限循环生成剩余的组合。
while True:
在循环内部,我们首先在索引列表中向后退一步,搜索尚未达到最大值的索引。我们使用上述公式 ( i + n - r
) 来确定给定索引的最大值。如果我们发现一个索引没有达到它的最大值,那么我们就跳出循环。
for i in reversed(range(r)):
if indices[i] != i + n - r:
break
如果我们没有找到一个,那么这意味着所有的索引都在它们的最大值,所以我们完成了迭代。(这使用了鲜为人知的for-else
结构;仅当循环正常终止else
时才执行该块。)for
else:
return
所以现在我们知道索引i
需要增加:
indices[i] += 1
另外,后面的索引i
都是最大值,所以需要重新设置。
for j in range(i+1, r):
indices[j] = indices[j-1] + 1
现在我们有了下一组索引,所以我们产生了另一个组合。
yield tuple(pool[i] for i in indices)
这种方法有几种变体;在另一种情况下,您不是在索引中倒退,而是向前迈进,增加第一个与下一个索引之间有“间隙”的索引,并重置较低的索引。
最后,您也可以递归地定义它,尽管从实用上讲,递归定义可能不会那么有效。