python itertools.permutations 的算法
Posted
技术标签:
【中文标题】python itertools.permutations 的算法【英文标题】:algorithm for python itertools.permutations 【发布时间】:2011-02-03 16:04:25 【问题描述】:有人能解释一下 Python 标准库 2.6 中 itertools.permutations
例程的算法吗?我不明白它为什么会起作用。
代码是:
def permutations(iterable, r=None):
# permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
# permutations(range(3)) --> 012 021 102 120 201 210
pool = tuple(iterable)
n = len(pool)
r = n if r is None else r
if r > n:
return
indices = range(n)
cycles = range(n, n-r, -1)
yield tuple(pool[i] for i in indices[:r])
while n:
for i in reversed(range(r)):
cycles[i] -= 1
if cycles[i] == 0:
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
else:
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
yield tuple(pool[i] for i in indices[:r])
break
else:
return
【问题讨论】:
【参考方案1】:结果中的模式比文字更容易回答(除非你想知道理论的数学部分), 所以打印出来是最好的解释方式。 最微妙的是, 循环到最后,它会自动重置到上一轮的第一圈,然后开始下一轮循环,或者不断地重置到最后一轮的第一圈,甚至更大的一轮,就像一个时钟。
执行重置工作的代码部分:
if cycles[i] == 0:
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
整体:
In [54]: def permutations(iterable, r=None):
...: # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
...: # permutations(range(3)) --> 012 021 102 120 201 210
...: pool = tuple(iterable)
...: n = len(pool)
...: r = n if r is None else r
...: if r > n:
...: return
...: indices = range(n)
...: cycles = range(n, n-r, -1)
...: yield tuple(pool[i] for i in indices[:r])
...: print(indices, cycles)
...: while n:
...: for i in reversed(range(r)):
...: cycles[i] -= 1
...: if cycles[i] == 0:
...: indices[i:] = indices[i+1:] + indices[i:i+1]
...: cycles[i] = n - i
...: print("reset------------------")
...: print(indices, cycles)
...: print("------------------")
...: else:
...: j = cycles[i]
...: indices[i], indices[-j] = indices[-j], indices[i]
...: print(indices, cycles, i, n-j)
...: yield tuple(pool[i] for i in indices[:r])
...: break
...: else:
...: return
部分结果:
In [54]: list(','.join(i) for i in permutations('ABCDE', 3))
([0, 1, 2, 3, 4], [5, 4, 3])
([0, 1, 3, 2, 4], [5, 4, 2], 2, 3)
([0, 1, 4, 2, 3], [5, 4, 1], 2, 4)
reset------------------
([0, 1, 2, 3, 4], [5, 4, 3])
------------------
([0, 2, 1, 3, 4], [5, 3, 3], 1, 2)
([0, 2, 3, 1, 4], [5, 3, 2], 2, 3)
([0, 2, 4, 1, 3], [5, 3, 1], 2, 4)
reset------------------
([0, 2, 1, 3, 4], [5, 3, 3])
------------------
([0, 3, 1, 2, 4], [5, 2, 3], 1, 3)
([0, 3, 2, 1, 4], [5, 2, 2], 2, 3)
([0, 3, 4, 1, 2], [5, 2, 1], 2, 4)
reset------------------
([0, 3, 1, 2, 4], [5, 2, 3])
------------------
([0, 4, 1, 2, 3], [5, 1, 3], 1, 4)
([0, 4, 2, 1, 3], [5, 1, 2], 2, 3)
([0, 4, 3, 1, 2], [5, 1, 1], 2, 4)
reset------------------
([0, 4, 1, 2, 3], [5, 1, 3])
------------------
reset------------------(bigger reset)
([0, 1, 2, 3, 4], [5, 4, 3])
------------------
([1, 0, 2, 3, 4], [4, 4, 3], 0, 1)
([1, 0, 3, 2, 4], [4, 4, 2], 2, 3)
([1, 0, 4, 2, 3], [4, 4, 1], 2, 4)
reset------------------
([1, 0, 2, 3, 4], [4, 4, 3])
------------------
([1, 2, 0, 3, 4], [4, 3, 3], 1, 2)
([1, 2, 3, 0, 4], [4, 3, 2], 2, 3)
([1, 2, 4, 0, 3], [4, 3, 1], 2, 4)
【讨论】:
【参考方案2】:您需要了解permutation cycles 的数学理论,也称为“轨道”(了解这两个“艺术术语”很重要,因为combinatorics 的核心数学主题非常先进,并且您可能需要查找research papers 可以使用其中一个或两个术语)。
对于排列理论的简单介绍,wikipedia 可以提供帮助。如果您对组合学足够着迷并想要进一步探索并获得真正的理解,我提到的每个 URL 都提供了合理的参考书目(我个人是这样做的——这对我来说已经成为一种爱好;-)。
一旦你理解了数学理论,代码对于“逆向工程”来说仍然是微妙而有趣的。显然,indices
只是池中索引方面的当前排列,因为产生的项目总是由下式给出
yield tuple(pool[i] for i in indices[:r])
所以这个迷人的机器的核心是cycles
,它代表排列的轨道并导致indices
被更新,主要是通过语句
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
即,如果cycles[i]
是j
,这意味着索引的下一次更新是将第 i 个(从左侧)与第 j 个从右侧交换(例如,如果j
为1,则indices
的last 元素正在被交换——indices[-1]
)。然后当 cycles
的项目在其递减期间达到 0 时,“批量更新”的频率就会降低:
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
这将indices
的第i
项放在最后,将索引的所有后续项向左移动一个,并表示下次我们来到cycles
的该项时,我们将将indices
的新i
th 项(左起)与n - i
th 项(右起)交换——这将是i
th 再次,当然除了有将是一个
cycles[i] -= 1
在我们下一次检查之前;-)。
困难的部分当然是证明这是可行的 - 即,所有排列都是详尽生成的,没有重叠和正确的“定时”退出。我认为,在简单情况下完全暴露时,可能更容易查看机器如何工作——注释掉yield
语句并添加print
语句(Python 2.*),而不是证明,我们有
def permutations(iterable, r=None):
# permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
# permutations(range(3)) --> 012 021 102 120 201 210
pool = tuple(iterable)
n = len(pool)
r = n if r is None else r
if r > n:
return
indices = range(n)
cycles = range(n, n-r, -1)
print 'I', 0, cycles, indices
# yield tuple(pool[i] for i in indices[:r])
print indices[:r]
while n:
for i in reversed(range(r)):
cycles[i] -= 1
if cycles[i] == 0:
print 'B', i, cycles, indices
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
print 'A', i, cycles, indices
else:
print 'b', i, cycles, indices
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
print 'a', i, cycles, indices
# yield tuple(pool[i] for i in indices[:r])
print indices[:r]
break
else:
return
permutations('ABC', 2)
运行这个显示:
I 0 [3, 2] [0, 1, 2]
[0, 1]
b 1 [3, 1] [0, 1, 2]
a 1 [3, 1] [0, 2, 1]
[0, 2]
B 1 [3, 0] [0, 2, 1]
A 1 [3, 2] [0, 1, 2]
b 0 [2, 2] [0, 1, 2]
a 0 [2, 2] [1, 0, 2]
[1, 0]
b 1 [2, 1] [1, 0, 2]
a 1 [2, 1] [1, 2, 0]
[1, 2]
B 1 [2, 0] [1, 2, 0]
A 1 [2, 2] [1, 0, 2]
b 0 [1, 2] [1, 0, 2]
a 0 [1, 2] [2, 0, 1]
[2, 0]
b 1 [1, 1] [2, 0, 1]
a 1 [1, 1] [2, 1, 0]
[2, 1]
B 1 [1, 0] [2, 1, 0]
A 1 [1, 2] [2, 0, 1]
B 0 [0, 2] [2, 0, 1]
A 0 [3, 2] [0, 1, 2]
关注cycles
:它们从 3、2 开始——然后最后一个递减,所以 3、1——最后一个还不是零,所以我们有一个“小”事件(一个交换索引)并打破内循环。然后我们再次输入它,这次最后的减量给出 3, 0——最后一个现在为零,所以这是一个“大”事件——索引中的“质量交换”(这里没有太多的质量,但是,可能有;-) 并且循环回到 3、2。但是现在我们还没有中断 for 循环,所以我们继续减少 next-to-last (in这种情况下,第一个)——它给出了一个次要事件,一个交换索引,我们再次打破内部循环。回到循环,最后一个再次递减,这次给出 2、1 - 次要事件等。最终,整个 for 循环发生,只有主要事件,没有次要事件 - 这就是循环开始为所有事件的时候,因此减量使每个都为零(主要事件),在最后一个周期不会发生yield
。
由于没有在该循环中执行过break
,我们采用for
的else
分支,该分支返回。请注意,while n
可能有点误导:它实际上充当while True
-- n
永远不会改变,while
循环仅从该 return
语句中退出;它同样可以表示为if not n: return
,然后是while True:
,因为当然当n
是0
(空的“池”)之后,在第一个微不足道的空yield
之后就没有更多的东西了。作者只是决定通过将if not n:
与while
合并;-) 来节省几行。
我建议您继续研究一些更具体的案例——最终您应该会感觉到“发条”在运行。一开始只关注cycles
(可能相应地编辑print
语句,从它们中删除indices
),因为它们在轨道上的发条式进展是这个微妙而深入的算法的关键;一旦你了解那个,indices
正确更新以响应cycles
的顺序几乎是一种反高潮!-)
【讨论】:
只是我失去了希望,但总能指望亚历克斯!我还没有完全grok,但是你给出的线索非常好,我会读到的。多谢。你也知道是谁在 python 库中实现了这个吗? Raymond Hettinger:参见svn.python.org/view/python/trunk/Modules/… 的第 2495 行及以下。 循环列表代表什么?作为一个学习了 6 个学期的抽象代数并且对对称群和循环/轨道非常了解的人,这个符号(和代码)对我来说几乎没有任何意义。我不知道实际的总体策略是什么。 上面的链接坏了。请参阅here。以上是关于python itertools.permutations 的算法的主要内容,如果未能解决你的问题,请参考以下文章