簡體   English   中英

如何以組長度和組內元素的所有可能組合將列表拆分為 n 組?

[英]How to split a list into n groups in all possible combinations of group length and elements within group?

我想以所有可能的組合將一個列表分成 n 個組(允許可變組長度)。

說,我有以下清單:

lst=[1,2,3,4]

如果我指定 n=2,則列表可以分為 1 個元素 - 3 個元素或 2 個元素 - 2 個元素的組。 在這兩種拆分列表的方法中,每個列表中的元素都有獨特的組合。

當 n=2 時,這些將是:

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

當 n=1 時,這些將是:

(1,2,3,4)

當 n=3 時,這些將是:

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

我對長度為 0 的組不感興趣,組內的順序無關緊要。

我發現了兩個類似的問題,但它們並沒有完全回答我的問題。

這個問題將一個列表分成所有組合,其中每個組的長度為 n(我發現@tokland 的答案特別有用)。 但是,我並不希望所有組都具有相同的長度。

然后這個問題的第一步是獲取拆分位置的唯一組合,將列表拆分為 n 組。 但是,此處保留了列表順序,並且未確定這些組中元素的唯一組合。

我正在尋找這兩個問題的組合 - 一個列表被分成 n 組,所有可能的組長度組合以及組內元素的組合。

我們可以使用此答案中的基本遞歸算法並對其進行修改以生成特定長度的分區,而無需生成和過濾掉不需要的分區。

def sorted_k_partitions(seq, k):
    """Returns a list of all unique k-partitions of `seq`.

    Each partition is a list of parts, and each part is a tuple.

    The parts in each individual partition will be sorted in shortlex
    order (i.e., by length first, then lexicographically).

    The overall list of partitions will then be sorted by the length
    of their first part, the length of their second part, ...,
    the length of their last part, and then lexicographically.
    """
    n = len(seq)
    groups = []  # a list of lists, currently empty

    def generate_partitions(i):
        if i >= n:
            yield list(map(tuple, groups))
        else:
            if n - i > k - len(groups):
                for group in groups:
                    group.append(seq[i])
                    yield from generate_partitions(i + 1)
                    group.pop()

            if len(groups) < k:
                groups.append([seq[i]])
                yield from generate_partitions(i + 1)
                groups.pop()

    result = generate_partitions(0)

    # Sort the parts in each partition in shortlex order
    result = [sorted(ps, key = lambda p: (len(p), p)) for ps in result]
    # Sort partitions by the length of each part, then lexicographically.
    result = sorted(result, key = lambda ps: (*map(len, ps), ps))

    return result

這里發生了很多事情,所以讓我解釋一下。

首先,我們從上述遞歸算法的程序性、自下而上(術語?)實現開始:

def partitions(seq):
    """-> a list of all unique partitions of `seq` in no particular order.

    Each partition is a list of parts, and each part is a tuple.
    """
    n = len(seq)
    groups = []  # a list of lists, currently empty

    def generate_partitions(i):
        if i >= n:
            yield list(map(tuple, groups))
        else:
            for group in groups
                group.append(seq[i])
                yield from generate_partitions(i + 1)
                group.pop()

            groups.append([seq[i]])
            yield from generate_partitions(i + 1)
            groups.pop()

    if n > 0:
        return list(generate_partitions(0))
    else:
        return [[()]]

主要算法在嵌套的generate_partitions函數中。 基本上,它遍歷序列,對於每個項目,它: 1)將項目放入工作集中的每個當前組(也稱為部分)並遞歸; 2) 將項目放在它自己的新組中。

當我們到達序列的末尾 ( i == n ) 時,我們會生成我們一直在構建的工作集的(深層)副本。

現在,為了獲得特定長度的分區,我們可以簡單地過濾或分組我們正在尋找的結果並完成它,但是如果我們只是需要,這種方法會執行許多不必要的工作(即遞歸調用)長度為k分區。

請注意,在上面的函數中,分區的長度(即組數)在以下情況下會增加:

            # this adds a new group (or part) to the partition
            groups.append([seq[i]])
            yield from generate_partitions(i + 1)
            groups.pop()

……被處決。 因此,我們通過簡單地在該塊上放置一個保護來限制分區的大小,如下所示:

def partitions(seq, k):
    ...

    def generate_partitions(i):
        ...

            # only add a new group if the total number would not exceed k
            if len(groups) < k:
                groups.append([seq[i]])
                yield from generate_partitions(i + 1)
                groups.pop()

將新參數和僅該行添加到partitions函數現在將使其僅生成長度不超過k分區。 這幾乎就是我們想要的。 問題是for循環有時仍然會生成長度小於k分區。

為了修剪這些遞歸分支,我們只需要在可以確定序列中有足夠的剩余元素將工作集擴展到總共k組時才執行for循環。 剩余元素(或尚未放入組的元素)的數量是n - i (或len(seq) - i )。 k - len(groups)是我們需要添加以生成有效 k 分區的新組的數量。 如果n - i <= k - len(groups) ,那么我們不能通過將其添加到當前組之一來浪費項目——我們必須創建一個新組。

所以我們簡單地添加另一個守衛,這次添加到另一個遞歸分支:

    def generate_partitions(i):
        ...

            # only add to current groups if the number of remaining items
            # exceeds the number of required new groups.
            if n - i > k - len(groups):
                for group in groups:
                    group.append(seq[i])
                    yield from generate_partitions(i + 1)
                    group.pop()

            # only add a new group if the total number would not exceed k
            if len(groups) < k:
                groups.append([seq[i]])
                yield from generate_partitions(i + 1)
                groups.pop()

有了它,你就有了一個可用的 k 分區生成器。 您可能會進一步折疊一些遞歸調用(例如,如果還有 3 個剩余的項目,而我們還需要 3 個組,那么您已經知道必須將每個項目分成自己的組),但我想展示功能是對生成所有分區的基本算法的輕微修改。

剩下要做的就是對結果進行排序。 不幸的是,我沒有弄清楚如何以所需的順序直接生成分區(一個更聰明的狗的練習),而是作弊並在生成后進行排序。

def sorted_k_partitions(seq, k):
    ...
    result = generate_partitions(0)

    # Sort the parts in each partition in shortlex order
    result = [sorted(ps, key = lambda p: (len(p), p)) for ps in result]
    # Sort partitions by the length of each part, then lexicographically.
    result = sorted(result, key = lambda ps: (*map(len, ps), ps))

    return result

有點不言自明,除了關鍵功能。 第一個:

key = lambda p: (len(p), p) 

表示按長度排序序列,然后按序列本身排序(在 Python 中,默認情況下按字典順序排序)。 p代表“部分”。 這用於對分區內的部件/組進行排序。 這個鍵意味着,例如, (4,)(1, 2, 3) ,因此[(1, 2, 3), (4,)]被排序為[(4,), (1, 2, 3)]

key = lambda ps: (*map(len, ps), ps) 
# or for Python versions <3.5: lambda ps: tuple(map(len, ps)) + (ps,)

這里的ps代表“部分”,復數。 這個是說按每個元素的長度(必須是序列本身)對序列進行排序,然后(按字典順序)按序列本身排序。 這用於相對於彼此對分區進行排序,例如, [(4,), (1, 2, 3)]位於[(1, 2), (3, 4)]

以下內容:

seq = [1, 2, 3, 4]

for k in 1, 2, 3, 4:
    for groups in sorted_k_partitions(seq, k):
        print(k, groups)

產生:

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

最簡單的選擇是使用more_itertools庫。

from more_itertools import set_partitions

a = [1, 2, 3, 4]

# pass as the second argument the number of splits
list(set_patitions(a, 2))
...   
 [[[1], [2, 3, 4]],
 [[1, 2], [3, 4]],
 [[2], [1, 3, 4]],
 [[1, 2, 3], [4]],
 [[2, 3], [1, 4]],
 [[1, 3], [2, 4]],
 [[3], [1, 2, 4]]]
>>>

list(set_patitions(a, 3))
...
[[[1], [2], [3, 4]],
 [[1], [2, 3], [4]],
 [[1], [3], [2, 4]],
 [[1, 2], [3], [4]],
 [[2], [1, 3], [4]],
 [[2], [3], [1, 4]]]
>>>

set_partitions 使用生成器,允許立即創建數千個集合。

繼從@friendly_dog的有用的鏈接,我試圖通過調整使用的功能,回答我的問題這個職位。 我有一個粗略的解決方案,雖然我擔心它不是特別有效並且可以使用一些改進。 我最終生成了比我需要的多得多的分區集,然后過濾掉那些僅按排序順序不同的分區。

首先,我從Python 中的 Set partitions 中獲取這 3 個函數:

import itertools
from copy import deepcopy

def slice_by_lengths(lengths, the_list):
    for length in lengths:
        new = []
        for i in range(length):
            new.append(the_list.pop(0))
        yield new

def partition(number):
    return {(x,) + y for x in range(1, number) for y in partition(number-x)} | {(number,)}

def subgrups(my_list):
    partitions = partition(len(my_list))
    permed = []
    for each_partition in partitions:
        permed.append(set(itertools.permutations(each_partition, len(each_partition))))

    for each_tuple in itertools.chain(*permed):
        yield list(slice_by_lengths(each_tuple, deepcopy(my_list)))

然后我編寫了一個函數來包裝subgrups函數並將其應用於原始列表的每個排列。 我遍歷這些子組排列,如果它們的長度等於所需的分區數,我會以一種允許我識別重復項的方式對它們進行排序。 我不確定是否有更好的方法來解決這個問題。

def return_partition(my_list,num_groups):
    filtered=[]
    for perm in itertools.permutations(my_list,len(my_list)):
        for sub_group_perm in subgrups(list(perm)):
            if len(sub_group_perm)==num_groups:
                #sort  within each partition
                sort1=[sorted(i) for i in sub_group_perm]
                #sort by first element of each partition
                sort2=sorted(sort1, key=lambda t:t[0])
                #sort by the number of elements in each partition
                sort3=sorted(sort2, key=lambda t:len(t))
                #if this new sorted set of partitions has not been added, add it
                if sort3 not in filtered:
                    filtered.append(sort3)
    return filtered

在我的原始示例列表上運行它,我看到它產生了所需的輸出,並在兩個分區和三個分區上進行了測試。

>>> for i in return_partition([1,2,3,4],2):
...     print i    
... 
[[1], [2, 3, 4]]
[[4], [1, 2, 3]]
[[1, 2], [3, 4]]
[[3], [1, 2, 4]]
[[1, 3], [2, 4]]
[[2], [1, 3, 4]]
[[1, 4], [2, 3]]
>>> 

>>> for i in return_partition([1,2,3,4],3):
...     print i        
... 
[[1], [4], [2, 3]]
[[3], [4], [1, 2]]
[[1], [2], [3, 4]]
[[1], [3], [2, 4]]
[[2], [4], [1, 3]]
[[2], [3], [1, 4]]
>>> 
from more_itertools import set_partition
iterable = '1234'
for part in set_partitions(iterable):
    print([''.join(p) for p in part])



['1234']
['1', '234']
['12', '34']
['2', '134']
['123', '4']
['23', '14']
['13', '24']
['3', '124']
['1', '2', '34']
['1', '23', '4']
['1', '3', '24']
['12', '3', '4']
['2', '13', '4']
['2', '3', '14']
['1', '2', '3', '4']

https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.set_partitions

暫無
暫無

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

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