从两个列表创建所有可能的项组合的元组,而不复制元组中的项

时间:2012-10-23 17:33:35

标签: python combinatorics

我希望能够获取一系列数字并返回包含三元组的列表而不重复。 x的每个元素应该在三元组的每个位置出现一次。目标是获得如下内容:

get_combinations_without_duplicates(3) = [(0, 1, 2), (1, 2, 0), (2, 0, 1)]

对于范围(3),这只是一个列表旋转,但对于更高的范围,有更多可能的组合。我希望能够随机生成满足这些约束条件的三元组列表。

假设我们首先为n = 4的情况指定每个三元组的第一个元素:

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

第一个三元组的第二个元素可以是除0之外的任何元素。一旦选择了其中一个元素,那么这将限制下一个三元组的选项,依此类推。目标是使用一个函数来获取数字并以这种方式创建三元组,但并不总是创建相同的三元组。也就是说,最终结果可能是轮换:

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

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

以下是此功能的实现:

def get_combinations_without_duplicates(n):
    output = []
    second = range(n)
     third = range(n)
for i in range(n):
    triple = [i]
    #Get the second value of the triple, but make sure that it isn't a 
    #duplicate of the first value
    #in the triple or any value that has appeared in the second position of any triple
    choices_for_second = [number for number in second if number not in triple]
    #Randomly select a number from the allowed possibilities
    n_second = random.choice(choices_for_second) 
    #Append it to the triple
    triple.append(n_second)
    #Remove that value from second so that it won't be chosen for other triples
    second = [number for number in second if number != n_second]
    #Do the same for the third value
    choices_for_third = [number for number in third if number not in triple]
    n_third = random.choice(choices_for_third)
    triple.append(n_third)
    third = [number for number in third if number != n_third]
    output.append(tuple(triple))
return output

如下所述,此过程有时会随机选择不起作用的组合。如果您执行以下操作,则可以处理:

def keep_trying(n):
    try:
        return get_combinations_without_duplicates(n)
    except IndexError:
        return keep_trying(n)

但是,我想知道是否有更好的方法来做到这一点。

6 个答案:

答案 0 :(得分:5)

让我们再试一次。

一些观察结果。

  1. 在元组的排序数组中,第一个值始终为零。
  2. 数组的长度始终与数组中存在的元组数一样长。
  3. 您希望这些随机生成。
  4. 元组是按照'排序'生成的。订购。
  5. 根据这些规范,我们可以提出一种程序方法;

    1. 生成2个串行整数列表,一个用于选择,另一个用于种子。
    2. 对于种子列表中的每个数字[0, 1, 2, 3],随机追加并删除元素中尚未包含的数字。 [01, 13, 20, 32]
    3. 生成另一个串行整数列表,然后重复。 [012, 130, 203, 321]
    4. 但是,这不起作用。对于某些迭代,它将自己回到角落而不能生成数字。例如,[01, 13, 20, 32].. appending [3, 0, 1... crap, I'm stuck.

      解决这个问题的唯一方法是在整个行上进行 true 改组,然后重新洗牌,直到一个适合。这可能需要相当长的时间,并且随着设置变长而变得更加痛苦。

      所以,从程序上讲:

      解决方案1:随机生成

      1. 使用您的范围填充列表。 [0,1,2,3]
      2. 创建另一个列表。 [0,1,2,3]
      3. 随机播放清单。 [1,0,2,3]
      4. 随机播放,直至找到适合的...... [1,2,3,0]
      5. 重复第三个元素。
      6. 通过此过程,虽然计算机可以非常快速验证解决方案,但它无法非常快速地生成解决方案。但是,它只是产生真正随机答案的两种方法之一。

        因此,最快保证的方法是使用验证程序,而不是生成程序。首先,产生所有可能的排列。

        from itertools import permutations
        
        n = 4
        candidates = [i for i in permutations(xrange(n),3)]
        

        然后

        解决方案2:随机验证

        1. 选择一个以0开头的三元组。
        2. 随机弹出一个不以0开头的三连音。
        3. 验证随机选取的三联体是否为中间溶液。
        4. 如果没有,请弹出另一个三连组。
        5. 如果是,请附加三元组,然后重新加载三重队列
        6. 重复n次。 #或直到你耗尽队列,此时重复n次自然变为TRUE
        7. 下一个三联体的解决方案在数学上保证在解决方案集中,因此如果你让它耗尽自己,应该出现一个随机解决方案。这种方法的问题在于,并不能保证每个可能的结果都具有相等的概率。

          解决方案3:迭代验证

          对于等概率结果,去除随机化,并生成每个可能的3元组合,n列表长 - 并验证每个解决方案候选者。

          在候选解决方案列表上编写一个验证的函数,以生成每个解决方案,然后从该列表中随机弹出解决方案。

          from itertools import combinations
          
          results = [verify(i) for i in combinations(candidates, n)]
          # this is 10626 calls to verify for n=4, 5 million for n=5 
          # this is not an acceptable solution.  
          

          解决方案1或3都不是非常快,O(n ** 2),但是根据您的标准,如果您想要一个真正随机的解决方案,这可能会达到最快速度。解决方案2将保证是这三者中最快的,通常大大超过1或3,解决方案3具有最稳定的结果。您选择的这些方法中的哪一种取决于您想要对输出执行的操作。

          之后:

          最终,代码的速度将取决于您希望代码的随机。吐出满足您要求的元组系列的第一个(也是唯一的第一个)实例的算法可以快速运行,因为它只是按顺序攻击排列,一次,它将在O(n)时间内运行。但是,它不会随意做任何事......

          另外,这里有一些验证(i)的快速代码。它基于观察到两个元组在同一索引中可能没有相同的数字。

          def verify(t):
              """ Verifies that a set of tuples satisfies the combinations without duplicates condition. """
              zipt = zip(*t)
              return all([len(i) == len(set(i)) for i in zipt])
          

          n = 4完整解决方案集

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

          n = 5有552个独特的解决方案。这是第20个。

          ((0, 1, 2), (1, 0, 3), (2, 3, 4), (3, 4, 0), (4, 2, 1))
          ((0, 1, 2), (1, 0, 3), (2, 3, 4), (3, 4, 1), (4, 2, 0))
          ((0, 1, 2), (1, 0, 3), (2, 4, 0), (3, 2, 4), (4, 3, 1))
          ((0, 1, 2), (1, 0, 3), (2, 4, 1), (3, 2, 4), (4, 3, 0))
          ((0, 1, 2), (1, 0, 4), (2, 3, 0), (3, 4, 1), (4, 2, 3))
          ((0, 1, 2), (1, 0, 4), (2, 3, 1), (3, 4, 0), (4, 2, 3))
          ((0, 1, 2), (1, 0, 4), (2, 4, 3), (3, 2, 0), (4, 3, 1))
          ((0, 1, 2), (1, 0, 4), (2, 4, 3), (3, 2, 1), (4, 3, 0))
          ((0, 1, 2), (1, 2, 0), (2, 3, 4), (3, 4, 1), (4, 0, 3))
          ((0, 1, 2), (1, 2, 0), (2, 4, 3), (3, 0, 4), (4, 3, 1))
          ((0, 1, 2), (1, 2, 3), (2, 0, 4), (3, 4, 0), (4, 3, 1))
          ((0, 1, 2), (1, 2, 3), (2, 0, 4), (3, 4, 1), (4, 3, 0))
          ((0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 0), (4, 0, 1))
          ((0, 1, 2), (1, 2, 3), (2, 4, 0), (3, 0, 4), (4, 3, 1))
          ((0, 1, 2), (1, 2, 3), (2, 4, 1), (3, 0, 4), (4, 3, 0))
          ((0, 1, 2), (1, 2, 4), (2, 0, 3), (3, 4, 0), (4, 3, 1))
          ((0, 1, 2), (1, 2, 4), (2, 0, 3), (3, 4, 1), (4, 3, 0))
          ((0, 1, 2), (1, 2, 4), (2, 3, 0), (3, 4, 1), (4, 0, 3))
          ((0, 1, 2), (1, 2, 4), (2, 3, 1), (3, 4, 0), (4, 0, 3))
          ((0, 1, 2), (1, 2, 4), (2, 4, 3), (3, 0, 1), (4, 3, 0))
          

          因此,您可以生成这样的解决方案,但这需要时间。如果你打算利用这个,我会缓存按原样生成的解决方案,然后当你需要它们时随机拉出它们。顺便说一句,n = 5花了不到一分钟才完成,蛮力。由于解是O(n ** 2),我预计n = 6需要一个多小时,n = 7一天。回归真正的随机解决方案的唯一方法就是这样做。

          已编辑:无均衡分布的随机解决方案:

          以下是我在尝试解决此问题时编写的代码,解决方案2 的实现。我想我会发布它,因为它是一个部分的,不相等的分发解决方案,并且生成所有可能的解决方案,保证,给予足够的时间。

          def seeder(n):
              """ Randomly generates the first element in a solution. """
              seed = [0]
              a = range(1, n)
              for i in range(1, 3):
                  seed.append(a.pop(random.randint(0,len(a)-1)))
              return [seed]
          
          def extend_seed(seed, n):
              """ Semi-Randomly generates the next element in a solution. """
              next_seed = [seed[-1][0] + 1]
              candidates = range(0, n)
              for i in range(1, 3):
                  c = candidates[:]
                  for s in next_seed:
                      c.remove(s)
                  for s in seed:
                      try:
                          c.remove(s[i])
                      except ValueError:
                          pass
                  next_seed.append(c.pop(random.randint(0,len(c)-1)))
              seed.append(next_seed)
              return seed
          
          def combo(n):
              """ Extends seed until exhausted. 
              Some random generations return results shorter than n. """
              seed = seeder(n)
              while True:
                  try:
                      extend_seed(seed, n)
                  except (ValueError, IndexError):
                      return seed
          
          def combos(n):
              """ Ensures that the final return is of length n. """
              while True:
                  result = combo(n)
                  if len(result) == n:
                      return result
          

答案 1 :(得分:2)

你基本上想要一个Latin square焦虑的数字网格,其中每一行和每一列只包含一个数字,除了你只关心每一行中的前三个数字(一个拉丁矩形)。

<强>更新

我已经删除了我无效的代码示例,因为生成具有相同概率的随机拉丁方是非平凡的,如in a question on math.stackexchange.com所述。

SAGE project实现了该问题中提到的算法,因此您可以查看代码以获取灵感。

或者,如果您真的想了解详细信息,请查看this paper了解生成随机拉丁矩形的具体情况。

答案 2 :(得分:1)

实际上,itertools已经为你解决了这个问题。

import itertools

allp = [x for x in itertools.permutations(range(3))]
print allp

mylist = ['A','B','C']
allp2 = [x for x in itertools.permutations(mylist)]
print allp2

输出

[(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
[('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'), ('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')]

答案 3 :(得分:0)

对您的问题只是一个不同的观点。看看这是否适合你

>>> from itertools import chain,combinations
>>> def get_combinations_without_duplicates(iterable):
        return (tuple(chain(*(set(iterable) - set(e) , e))) for e in combinations(iterable,2))

>>> list(get_combinations_without_duplicates(range(3)))
[(2, 0, 1), (1, 0, 2), (0, 1, 2)]

答案 4 :(得分:0)

简单的列表轮换为所有n&gt; = 3:

提供了正确的解决方案

考虑n = 5的旋转解:

[
    (0, 1, 2),
    (1, 2, 3),
    (2, 3, 4),
    (3, 4, 0),
    (4, 0, 1)
]

每个位置只出现一次,每个位置都存在所有数字。


通常,len(get_combinations_without_duplicates(n)) == n代表n> = 3

答案 5 :(得分:0)

这是一种利用deque.rotate

的方法
>>> datax = []
>>> from collections import deque
>>> data_length = 10
>>> subset_length = 3
>>> for i in xrange(0, subset_length):
...     datax.append(deque(xrange(data_length)))
...
>>> for i in xrange(0, subset_length):
...     datax[i].rotate(i)
...
>>> print zip(*datax)
[(0, 9, 8), (1, 0, 9), (2, 1, 0), (3, 2, 1), (4, 3, 2), (5, 4, 3), (6, 5, 4), (7, 6, 5), (8, 7, 6), (9, 8, 7)]