python itertools.permutations的算法

时间:2010-04-02 07:54:06

标签: python algorithm permutation

有人可以在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

2 个答案:

答案 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,因为当returnif 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)