【问题标题】:Creating tuples of all possible combinations of items from two lists, without duplicating items within tuples从两个列表中创建所有可能的项目组合的元组,而不在元组中复制项目
【发布时间】:2012-10-23 17:33:34
【问题描述】:

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

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

对于 range(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)

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

【问题讨论】:

  • 你在做什么似乎是武断的。您说 x 的每个元素应该在三元组的每个位置出现一次,但是在您的示例中,有两个三元组在第一个位置为零:(0, 1, 2) 和 (0, 2, 1)。你是如何挑选你想要使用的?
  • 对不起,列表中的三元组是 (0, 1, 2)、(1, 2, 0) 和 (2, 0, 1)。 0 出现在第一个三元组的第一个位置、第二个三元组的第二个位置和第三个三元组的第三个位置。
  • 据我了解,这是该范围内 3 个数字的每个可能子集的每次旋转。对吗?
  • 一件非常方便的事情(他说,删除他自己的解决方案)是让您提供一个蛮力解决方案,它可以提供您想要的答案。然后每个人都可以将他们对您的话的解释与代码的输出进行比较。
  • @wdnsd 我添加了一个实际的解决程序。

标签: python combinatorics


【解决方案1】:

让我们再试一次。

一些观察。

  1. 在已排序的元组数组中,第一个值始终为零。
  2. 数组的长度将始终与数组中存在的元组数一样长。
  3. 您希望这些是随机生成的。
  4. 元组按“排序”顺序生成。

根据这些规范,我们可以想出一个程序化的方法;

  1. 生成 2 个序列整数列表,一个用于选择,另一个用于种子。
  2. 对于种子列表中的每个数字,[0, 1, 2, 3],随机追加和删除元素中尚不存在的数字。 [01, 13, 20, 32]
  3. 生成另一个序列整数列表,然后重复。 [012, 130, 203, 321]

但是,这不起作用。对于某些迭代,它会将自己退回到角落并且无法生成数字。比如[01, 13, 20, 32].. appending [3, 0, 1... crap, I'm stuck.

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

所以,从程序上讲:

方案一:随机生成

  1. 用您的范围填充列表。 [0, 1, 2, 3]
  2. 创建另一个列表。 [0, 1, 2, 3]
  3. 随机播放列表。 [1, 0, 2, 3]
  4. 随机播放,直到找到适合的... [1, 2, 3, 0]
  5. 重复第三个元素。

使用此过程,虽然计算机可以非常快速地验证解决方案,但它不能非常快速地生成解决方案。然而,它只是生成真正随机答案的两种方法之一。

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

from itertools import permutations

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

那么。

方案二:随机验证

  1. 选择一个以 0 开头的三元组。
  2. 随机弹出一个不以 0 开头的三元组。
  3. 验证随机选取的三元组是否为中间解。
  4. 如果没有,请弹出另一个三元组。
  5. 如果是,请附加三元组,然后重新填充三元组队列
  6. 重复 n 次。 # 或者直到你耗尽队列,此时重复 n 次自然变为 TRUE

下一个三元组的解在数学上保证在解集中,所以如果你让它自己耗尽,应该会出现一个随机解。这种方法的问题在于不能保证每个可能的结果都有相等的概率。

解决方案 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 时从它们中随机提取。顺便说一句,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

【讨论】:

    【解决方案2】:

    您实际上想要一个Latin square,一个 n x n 的数字网格,其中每一行和每一列都只包含每个数字一次,除了您只关心每行中的前三个数字(拉丁矩形)。

    更新:

    我已经删除了无效的代码示例,因为生成具有相等概率的随机拉丁方格并非易事,正如 in a question on math.stackexchange.com 所讨论的那样。

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

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

    【讨论】:

    • 您的解决方案不会产生 ((0, 1, 2), (1, 0, 3), (2, 3, 0), (3, 2, 1)) 以及其他元组。此外,还有重复,您最终会得到[[0, 1, 2], [2, 3, 0], [1, 2, 3], [3, 0, 1]][[0, 1, 2], [2, 3, 0], [3, 0, 1], [1, 2, 3]],它们会切换最后两个元组。每个生成的集合都是有效集合,但随机化不起作用。
    • 感谢@kreativitea,你是对的——必须对符号进行排列以生成所有可能的组合。更新的代码应该可以解决这个问题。
    • 它仍然缺少六个解决方案--[[0, 1, 2], [1, 0, 3], [2, 3, 0], [3, 2, 1]] [[0, 1, 3], [1, 0, 2], [2, 3, 1], [3, 2, 0]] [[0, 2, 1], [1, 3, 0], [2, 0, 3], [3, 1, 2]] [[0, 2, 3], [1, 3, 2], [2, 0, 1], [3, 1, 0]] [[0, 3, 1], [1, 2, 0], [2, 1, 3], [3, 0, 2]] [[0, 3, 2], [1, 2, 3], [2, 1, 0], [3, 0, 1]] # 针对我的蛮力结果进行了测试
    【解决方案3】:

    其实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')]
    

    【讨论】:

    • 很确定 OP 想要所有长度的 mylist 的 3 元组
    • 你可以做list(itertools.permutations(range(3)))
    【解决方案4】:

    只是对您的问题有不同的看法。看看这是否适合你

    >>> 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)]
    

    【讨论】:

    • list(get_combinations_without_duplicates(range(4))) 失败 - (1, 3, 0, 2)(1, 2, 0, 3) 在第一个位置都有一个 1
    • 更不用说它们不是 3 元组,它们是 4 元组——OP 特别想要 3 元组。 @Abhijit
    • @kreativitea:我不知道我是怎么错过的。
    【解决方案5】:

    一个简单的列表轮换为所有 n >= 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

    【讨论】:

      【解决方案6】:

      这是一种利用 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)]
      

      【讨论】:

      • 这只是旋转解的一种变体,不会产生随机解。
      猜你喜欢
      • 1970-01-01
      • 2014-11-06
      • 1970-01-01
      • 1970-01-01
      • 2012-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多