有人可以在Python标准lib 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
答案 0 :(得分:28)
你需要理解permutation cycles的数学理论,也称为“轨道”(因为数学主题,combinatorics的核心,所以对于“艺术术语”都很重要。高级,您可能需要查找可以使用其中一个或两个术语的research papers。
为了更简单地介绍排列理论,wikipedia可以提供帮助。我提到的每个网址都提供了合理的参考书目,如果你对组合学足够了解,想要进一步探索它并获得真正的理解(我个人 - 这对我来说有点嗜好; - )。
一旦理解了数学理论,代码对于“逆向工程”来说仍然是微妙而有趣的。显然,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
这会将i
indices
项置于最后,将所有后续索引项目向左移动一位,并指示下次我们来到{{1我们将使用cycles
(来自右侧)交换i
的新indices
项(从左侧开始) - 这将是{{1}再一次,除了事实上会有一个
n - i
在我们下次检查之前; - )。
困难部分当然会证明这是有效的 - 即,所有排列都是穷尽生成的,没有重叠和正确的“定时”退出。我认为,在简单的情况下,可以更容易地看到机器在完全暴露时如何工作 - 而不是证明 - 评论i
语句并添加cycles[i] -= 1
语句(Python 2. * ),我们有
yield
运行此显示:
print
专注于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)
:他们从3开始 - 2 - 然后最后一个减少,所以3,1 - 最后一个不是零,所以我们有一个“小”事件(一个交换索引)并打破内循环。然后我们再次输入它,这次最后一次给出3,0 - 最后一个现在为零所以它是一个“大”事件 - 指数中的“质量交换”(这里没有太大的质量,但是,可能会有;-)并且循环回到3,2。但是现在我们还没有中断for循环,所以我们继续递减 next -to-last(in这种情况,第一个) - 它给出一个小事件,一个指数交换,我们再次打破内循环。回到循环,然后最后一个递减,这次给出2,1 - 次要事件等。最终整个for循环发生只有重大事件,没有次要事件 - 这是周期开始时所有的事件,因此减量将每个都归零(重大事件),在最后一个循环中没有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
,我们将返回yield
的{{1}}分支。请注意,break
可能有点误导:它实际上充当else
- for
从不更改,while n
循环仅退出while True
声明;它同样可以表示为n
后跟while
,因为当return
为if not n: return
(空“池”)时,在第一个之后没有什么可以产生的,琐碎的空while True:
。作者刚刚决定通过将n
检查与0
折叠来保存几行。 - /。
我建议你继续研究一些更具体的案例 - 最终你应该感觉到“发条”的运作。最初只关注yield
(可能会相应地编辑if not n:
语句,从中移除while
),因为他们通过轨道的类似钟表的进展是这个微妙和深度算法的关键;一旦你理解那个,cycles
响应print
的顺序而得到适当更新的方式几乎是一个反复无常的! - )
答案 1 :(得分:0)
结果中的模式比单词更容易回答(除非你想知道理论的数学部分), 所以打印出来是最好的解释方式 最微妙的是, 循环到最后,它会将自身重置为最后一轮的第一个转弯,然后开始下一个循环,或者连续重置到最后一个甚至更大的一轮的第一个转弯,就像一个时钟。
执行重置作业的代码部分:
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)