【问题标题】:algorithm for python itertools.permutationspython 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

【问题讨论】:

    标签: python algorithm permutation


    【解决方案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,则indiceslast 元素正在被交换——indices[-1])。然后当 cycles 的项目在其递减期间达到 0 时,“批量更新”的频率就会降低:

      indices[i:] = indices[i+1:] + indices[i:i+1]
      cycles[i] = n - i
      

      这将indices 的第i 项放在最后,将索引的所有后续项向左移动一个,并表示下次我们来到cycles 的该项时,我们将将indices 的新ith 项(左起)与n - ith 项(右起)交换——这将是ith 再次,当然除了有将是一个

      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,我们采用forelse 分支,该分支返回。请注意,while n 可能有点误导:它实际上充当while True -- n 永远不会改变,while 循环仅从该 return 语句中退出;它同样可以表示为if not n: return,然后是while True:,因为当然当n0(空的“池”)之后,在第一个微不足道的空yield 之后就没有更多的东西了。作者只是决定通过将if not n:while 合并;-) 来节省几行。

      我建议您继续研究一些更具体的案例——最终您应该会感觉到“发条”在运行。一开始只关注cycles(可能相应地编辑print语句,从它们中删除indices),因为它们在轨道上的发条式进展是这个微妙而深入的算法的关键;一旦你了解那个indices 正确更新以响应cycles 的顺序几乎是一种反高潮!-)

      【讨论】:

      • 只是我失去了希望,但总能指望亚历克斯!我还没有完全grok,但是你给出的线索非常好,我会读到的。多谢。你也知道是谁在 python 库中实现了这个吗?
      • Raymond Hettinger:参见svn.python.org/view/python/trunk/Modules/… 的第 2495 行及以下。
      • 循环列表代表什么?作为一个学习了 6 个学期的抽象代数并且对对称群和循环/轨道非常了解的人,这个符号(和代码)对我来说几乎没有任何意义。我不知道实际的总体策略是什么。
      • 上面的链接坏了。请参阅here
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-29
      • 1970-01-01
      • 2019-09-14
      • 1970-01-01
      • 1970-01-01
      • 2011-09-25
      • 2020-03-03
      相关资源
      最近更新 更多