簡體   English   中英

從兩個列表創建所有可能的項組合的元組,而不復制元組中的項

[英]Creating tuples of all possible combinations of items from two lists, without duplicating items within tuples

我希望能夠獲取一系列數字並返回包含三元組的列表而不重復。 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)

但是,我想知道是否有更好的方法來做到這一點。

讓我們再試一次。

一些觀察。

  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.

解決這個問題的唯一方法是在整個行上進行真正的改組,然后重新洗牌,直到一個適合。 這可能需要相當長的時間,並且隨着設置變長而變得更加痛苦。

所以,從程序上講:

解決方案1:隨機生成

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

然后。

解決方案2:隨機驗證

  1. 選擇一個以0開頭的三元組。
  2. 隨機彈出一個不以0開頭的三元組。
  3. 驗證隨機選取的三聯體是否為中間溶液。
  4. 如果沒有,請彈出另一個三元組。
  5. 如果是,請附加三元組,然后重新編寫TRIPLET QUEUE
  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 = 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

你基本上想要一個拉丁方 ,焦數網格,其中每一行和每一列只包含一個數字,除了你只關心每一行中的前三個數字(一個拉丁矩形)。

更新:

我已經刪除了我無效的代碼示例,因為生成具有相等概率的隨機拉丁方是非常重要的,正如math.stackexchange.com上的一個問題所討論的那樣

SAGE項目實現了該問題中提到的算法,因此您可以查看代碼以獲取靈感。

或者,如果您真的想了解詳細信息,請查看本文以了解生成隨機拉丁矩形的具體情況。

實際上,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')]

對你的問題只是一個不同的觀點。 看看這是否適合你

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

簡單的列表輪換為所有n> = 3提供了正確的解決方案:

考慮n = 5的旋轉解:

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

每個數字僅在每個位置出現一次,並且對於每個位置,所有數字都存在。


通常,對於n> = 3, len(get_combinations_without_duplicates(n)) == n

這是一種利用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)]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM