【问题标题】:How to generate permutations of a list without "reverse duplicates" in Python using generators如何使用生成器在 Python 中生成没有“反向重复”的列表排列
【发布时间】:2010-10-31 22:14:09
【问题描述】:

这与问题How to generate all permutations of a list in Python有关

如何生成符合以下条件的所有排列如果两个排列彼此相反(即 [1,2,3,4] 和 [4,3,2, 1]),它们被认为是相等的,只有其中一个应该是最终结果

例子:

permutations_without_duplicates ([1,2,3])
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]

我正在置换包含唯一整数的列表。

结果排列的数量会很高,所以如果可能的话,我想使用 Python 的生成器。

编辑:如果可能,我不想将所有排列的列表存储到内存中。

【问题讨论】:

    标签: python algorithm generator combinatorics


    【解决方案1】:

    我对 SilentGhost 的提议进行了精彩的跟进 - 发布单独的答案,因为评论的边距太窄而无法包含代码 :-)

    itertools.permutations 是内置的(从 2.6 开始)并且速度很快。我们只需要一个过滤条件,对于每个 (perm, perm[::-1]) 都将接受其中一个。由于 OP 说项目总是不同的,我们可以比较任意 2 个元素:

    for p in itertools.permutations(range(3)):
        if p[0] <= p[-1]:
            print(p)
    

    哪个打印:

    (0, 1, 2)
    (0, 2, 1)
    (1, 0, 2)
    

    这是可行的,因为颠倒排列总是会翻转第一个元素和最后一个元素之间的关系!

    对于 4 个或更多元素,其他围绕中间对称的元素对(例如每边第二个 p[1] &lt;= p[::-1][1])也可以。
    (这个答案之前声称p[0] &lt; p[1] 会起作用,但它不起作用——在 p 反转之后,它会选择不同的元素。)

    您还可以对整个排列与它的反向进行直接的字典比较:

    for p in itertools.permutations(range(3)):
        if p <= p[::-1]:
            print(p)
    

    我不确定是否有更有效的过滤方式。 itertools.permutations 保证字典顺序,但字典位置 pp[::-1] 以相当复杂的方式相关。特别是,只停在中间是行不通的。

    但我怀疑(没有检查)具有 2:1 过滤的内置迭代器将优于任何自定义实现。当然,它以简单取胜!

    【讨论】:

    • 优秀的评论开始一个很好的答案:) :)
    • 这个简单的测试仅在您的集合不包含重复项时才有效。否则,您将不得不按照 Steve Jessop 的建议进行完整的字典比较。
    • 比较应该是&lt;=而不是&lt;,所以它适用于n=1的特殊情况。
    • 哪个更好?我应该使用p[0] &lt;= p[-1] 还是p &lt;= p[::-1]
    【解决方案2】:

    一些设置代码首先:

    try:
        from itertools import permutations
    except ImportError:
        # straight from http://docs.python.org/library/itertools.html#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
    
    def non_reversed_permutations(iterable):
        "Return non-reversed permutations for an iterable with unique items"
        for permutation in permutations(iterable):
            if permutation[0] < permutation[-1]:
                yield permutation
    

    【讨论】:

    • 取决于具体版本似乎有点骇人听闻。为什么不尝试导入模块,如果失败定义函数?
    【解决方案3】:

    编辑:完全更改为将所有内容都保留为生成器(永远不会将整个列表保存在内存中)。应该满足要求(只计算可能排列的一半(而不是相反的排列)。 EDIT2:添加了来自here 的更短(更简单)的阶乘函数。

    EDIT3::(参见 cmets)- 可以在 bwopah's version 中找到改进的版本。

    def fac(x): 
        return (1 if x==0 else x * fac(x-1))
    
    def all_permutations(plist):
        global counter
    
        if len(plist) <=1:
            yield plist
        else:
            for perm in all_permutations(plist[1:]):
                for i in xrange(len(perm)+1):
                    if len(perm[:i] + plist[0:1] + perm[i:]) == lenplist:
                            if counter == limit:
                                 raise StopIteration
                            else:
                                 counter = counter + 1
                    yield perm[:i] + plist[0:1] + perm[i:]
    
    counter = 0
    plist = ['a','b','c']
    lenplist = len(plist)
    limit = fac(lenplist) / 2
    
    all_permutations_gen = all_permutations(plist)
    print all_permutations_gen
    print list(all_permutations_gen)
    

    【讨论】:

    • 计数器在这里不应该是全局的,它和本地一样有效。您也可以使用 counter += 1 代替 counter = counter + 1。
    • limit 和 lenplist 在本地也会更好
    • 对每个递归进行局部限制会使其更快,并使得:如果 len(perm[:i] + plist[0:1] + perm[i:]) == lenplist 不必要。跨度>
    • 在这里查看更优化的版本:stackoverflow.com/questions/960557/…
    • @Kiv, bpowah: 好点(这是一个快速版本 ;-) 我会调整我的版本,但由于 bpowah 发布了更优化的版本,我将改为链接到顶部邮政。谢谢!
    【解决方案4】:

    这是我的实现:

    a = [1,2,3,4]
    
    def p(l):
      if len(l) <= 1:
        yield l
      else:
        for i in range(len(l)):
          for q in p([l[j] for j in range(len(l)) if j != i]):
            yield [l[i]] + q
    
    out = (i for i in p(a) if i < i[::-1])
    

    P 函数是一个常规 permu 函数,产生所有可能性。迭代结果时过滤器完成。简单地说,它有两个可能的结果,所有 permus 的较小一半和 permus 的较大一半。在此示例中,out 包含列表的较小一半。

    【讨论】:

      【解决方案5】:

      这是 ChristopheD 接受的答案的更简洁和更快的版本,我非常喜欢。递归很棒。我通过删除重复项使其强制传入列表的唯一性,但是也许它应该只引发异常。

      def fac(x): 
          return (1 if x==0 else x * fac(x-1))
      
      def permz(plist):
          plist = sorted(set(plist))
          plen = len(plist)
          limit = fac(plen) / 2
          counter = 0
          if plen==1:
              yield plist
          else:
              for perm in permz(plist[1:]):
                  for i in xrange(plen):
                      if counter == limit:
                           raise StopIteration
                      counter += 1
                      yield perm[:i] + plist[0:1] + perm[i:]
      
      # ---- testing ----
      plists = [
          list('love'),
          range(5),
          [1,4,2,3,9],
          ['a',2,2.1],
          range(8)]               
      
      for plist in plists:
          perms = list(permz(plist))
          print plist, True in [(list(reversed(i)) in foo) for i in perms]
      

      【讨论】:

        【解决方案6】:

        下面是可以解决问题的代码。为了摆脱重复,我注意到对于您的列表,如果第一个位置的值大于最后一个位置的值,那么它将是一个重复。我创建了一个地图来跟踪每个项目在列表中的起始位置,然后使用该地图进行测试。该代码也不使用递归,因此它的内存占用很小。此外,列表可以是任何类型的项目,而不仅仅是数字,请参阅最后两个测试用例。

        这里是代码。

        class Permutation:
            def __init__(self, justalist):
                self._data = justalist[:]
                self._len=len(self._data)
                self._s=[]
                self._nfact=1
                self._map ={}
                i=0
                for elem in self._data:
                    self._s.append(elem)
                    self._map[str(elem)]=i
                    i+=1
                    self._nfact*=i
                if i != 0:
                    self._nfact2=self._nfact//i
        
            def __iter__(self):
                for k in range(self._nfact):
                    for i in range(self._len):
                        self._s[i]=self._data[i]
                    s=self._s
                    factorial=self._nfact2
                    for i in range(self._len-1):
                        tempi = (k // factorial) % (self._len - i)
                        temp = s[i + tempi]
                        for j in range(i + tempi,i,-1):
                            s[j] = s[j-1]
                        s[i] = temp
                        factorial //= (self._len - (i + 1))
        
                    if self._map[str(s[0])] < self._map[str(s[-1])]:
                        yield s
        
        
        
        
        s=[1,2]
        print("_"*25)
        print("input list:",s)
        for sx in Permutation(s):
            print(sx)
        
        s=[1,2,3]
        print("_"*25)
        print("input list:",s)
        for sx in Permutation(s):
            print(sx)
        
        s=[1,2,3,4]
        print("_"*25)
        print("input list:",s)
        for sx in Permutation(s):
            print(sx)
        
        s=[3,2,1]
        print("_"*25)
        print("input list:",s)
        for sx in Permutation(s):
            print(sx)
        
        s=["Apple","Pear","Orange"]
        print("_"*25)
        print("input list:",s)
        for sx in Permutation(s):
            print(sx)
        
        s=[[1,4,5],"Pear",(1,6,9),Permutation([])]
        print("_"*25)
        print("input list:",s)
        for sx in Permutation(s):
            print(sx)
        

        这是我的测试用例的输出。

        _________________________
        input list: [1, 2]
        [1, 2]
        _________________________
        input list: [1, 2, 3]
        [1, 2, 3]
        [1, 3, 2]
        [2, 1, 3]
        _________________________
        input list: [1, 2, 3, 4]
        [1, 2, 3, 4]
        [1, 2, 4, 3]
        [1, 3, 2, 4]
        [1, 3, 4, 2]
        [1, 4, 2, 3]
        [1, 4, 3, 2]
        [2, 1, 3, 4]
        [2, 1, 4, 3]
        [2, 3, 1, 4]
        [2, 4, 1, 3]
        [3, 1, 2, 4]
        [3, 2, 1, 4]
        _________________________
        input list: [3, 2, 1]
        [3, 2, 1]
        [3, 1, 2]
        [2, 3, 1]
        _________________________
        input list: ['Apple', 'Pear', 'Orange']
        ['Apple', 'Pear', 'Orange']
        ['Apple', 'Orange', 'Pear']
        ['Pear', 'Apple', 'Orange']
        _________________________
        input list: [[1, 4, 5], 'Pear', (1, 6, 9), <__main__.Permutation object at 0x0142DEF0>]
        [[1, 4, 5], 'Pear', (1, 6, 9), <__main__.Permutation object at 0x0142DEF0>]
        [[1, 4, 5], 'Pear', <__main__.Permutation object at 0x0142DEF0>, (1, 6, 9)]
        [[1, 4, 5], (1, 6, 9), 'Pear', <__main__.Permutation object at 0x0142DEF0>]
        [[1, 4, 5], (1, 6, 9), <__main__.Permutation object at 0x0142DEF0>, 'Pear']
        [[1, 4, 5], <__main__.Permutation object at 0x0142DEF0>, 'Pear', (1, 6, 9)]
        [[1, 4, 5], <__main__.Permutation object at 0x0142DEF0>, (1, 6, 9), 'Pear']
        ['Pear', [1, 4, 5], (1, 6, 9), <__main__.Permutation object at 0x0142DEF0>]
        ['Pear', [1, 4, 5], <__main__.Permutation object at 0x0142DEF0>, (1, 6, 9)]
        ['Pear', (1, 6, 9), [1, 4, 5], <__main__.Permutation object at 0x0142DEF0>]
        ['Pear', <__main__.Permutation object at 0x0142DEF0>, [1, 4, 5], (1, 6, 9)]
        [(1, 6, 9), [1, 4, 5], 'Pear', <__main__.Permutation object at 0x0142DEF0>]
        [(1, 6, 9), 'Pear', [1, 4, 5], <__main__.Permutation object at 0x0142DEF0>]
        

        【讨论】:

          【解决方案7】:

          这是一个人的建议的实现

          来自http://en.wikipedia.org/wiki/Permutation#Lexicographical_order_generation 以下算法在给定排列之后按字典顺序生成下一个排列。它就地改变了给定的排列。

          1. 找到满足 s[i]
          2. 找到满足 s[j] > s[i] 的最高索引 j > i。这样的 j 必须存在,因为 i+1 就是这样的索引。
          3. 用 s[j] 交换 s[i]。
          4. 反转索引 i 之后所有元素的所有顺序

          功能:

          def perms(items):
              items.sort()
              yield items[:]
              m = [len(items)-2]  # step 1
              while m:
                  i = m[-1]
                  j = [ j for j in range(i+1,len(items)) if items[j]>items[i] ][-1] # step 2
                  items[i], items[j] = items[j], items[i] # step 3
                  items[i+1:] = list(reversed(items[i+1:])) # step 4
                  if items<list(reversed(items)):
                      yield items[:]
                  m = [ i for i in range(len(items)-1) if items[i]<items[i+1] ]  # step 1
          

          检查我们的工作:

          >>> foo=list(perms([1,3,2,4,5]))
          >>> True in [(list(reversed(i)) in foo) for i in foo]
          False
          

          【讨论】:

            【解决方案8】:

            如果您按字典顺序生成排列,那么您不需要存储任何东西来确定是否已经看到给定排列的反向。您只需按字典顺序将其与其反向进行比较 - 如果它更小则返回它,如果它更大则跳过它。

            可能有一种更有效的方法来做到这一点,但这很简单并且具有您需要的属性(可作为生成器实现,使用 O(n) 工作内存)。

            【讨论】:

              【解决方案9】:

              这个怎么样:

              from itertools import permutations
              
              def rev_generator(plist):
                  reversed_elements = set()
                  for i in permutations(plist):
                      if i not in reversed_elements:
                          reversed_i = tuple(reversed(i))
                          reversed_elements.add(reversed_i)
                          yield i
              
              >>> list(rev_generator([1,2,3]))
              [(1, 2, 3), (1, 3, 2), (2, 1, 3)]
              

              另外,如果返回值必须是一个列表,您可以将 yield i 更改为 yield list(i),但出于迭代目的,元组可以正常工作。

              【讨论】:

              • 在内存中保留排列列表(reversed_elements),我想避免。
              • 你为什么使用 zip(*reversed(zip(i)))[0] 而不是 list(reversed(i)) ?
              • 我稍微整理了一下代码,在 python 2.6 和 3.0 中工作
              • Nadia:不知道 Tuple 构造函数,并决定聪明而不是查找它。 :P 一个更直接的答案:它需要是一个元组,而不是一个列表。
              【解决方案10】:

              itertools.permutations 完全符合您的要求。你也可以使用reversed built-in

              【讨论】:

              • 这会给我所有的排列,而我正好需要其中的一半。 itertools.permutations([1,2,3]) 将包含 [1,2,3] 和 [3,2,1] 我只需要其中一个。
              • 那么,有什么问题吗?找到排列的反转版本并将其删除。检查最终列表的大小是否为 itertools.permutations 结果大小的一半
              • 我认为 itertools.permutations 是在 python 2.6 中引入的?这可能是也可能不是问题。
              • @SilentGhost,这种方法要求我在内存中有所有排列,我想避免这种情况。 @CristopheD,2.6 没问题
              • 正好一半?长度为 n 的列表的排列数为 2^n。但是,如果列表中的所有元素都相同,那么您希望迭代器仅返回 一个 元素,我猜?
              猜你喜欢
              • 2016-02-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-10-22
              • 2011-03-07
              相关资源
              最近更新 更多