簡體   English   中英

將 a 項的所有組合列出到 b 個大小為 c 的組中

[英]List all combinations of a items into b groups of size c

我正在尋找一種在 Python 中將任意數量的項目拆分為任意數量的偶​​數組的方法,並獲取所有這些拆分的列表/數組。

因此,例如,給定 12 個項目,有 5775 種方法可以將它們分成 3 組,每組 4 個。計算這不是問題,但我似乎找不到返回這些 5775 的列表或數組的方法。我可以使用以下方法獲取第一組:

import itertools
list(itertools.combinations(range(12), 4))

但是我怎樣才能從中獲得剩余的組呢?

a = 4b = 2c = 2的所需輸出將是:

[[[1, 2], [3, 4]],
 [[1, 3], [2, 4]],
 [[1, 4], [2, 3]]]

對於a = 3b = 3c = 1

[[[1], [2], [3]]]

您可以使用遞歸生成器,它在每個級別或遞歸中計算(剩余)項目的所有組合。 下一個遞歸級別僅接收那些尚未在當前級別中使用的項目(其余)。

為了防止在組排序方面出現重復,我們需要截斷it.combinations的輸出,這樣它就不會產生之前迭代的剩余部分中出現的組合。 n為項目數, g為每組的大小。 然后it.combinations中的第一項是(0, 1, ..., g-1) (就索引而言)。 it.combinations中的當前項目為(0, g, g+1, ..., 2*g-1)時,項目(1, 2, ..., g)將成為余數的一部分(假設n % g == 0 )。 因此,我們需要截斷it.combinations的輸出,以使第一個元素是固定的(在上面的示例中為0 )。 因為it.combinations按字典順序生成項目,所以這涵蓋了第一個(n-1)! / (ng)! / (g-1)! (n-1)! / (ng)! / (g-1)! 項目( !表示階乘)。

下面是一個示例實現:

import itertools as it
from math import factorial
from typing import Iterator, Sequence, Tuple, TypeVar


T = TypeVar('T')


def group_items(items: Sequence[T], group_size: int) -> Iterator[Tuple[Tuple[T, ...], ...]]:
    if len(items) % group_size != 0:
        raise ValueError(
            f'Number of items is not a multiple of the group size '
            f'({len(items)} and {group_size})'
        )
    elif len(items) == group_size:
        yield (tuple(items),)
    elif items:
        count, _r = divmod(
            factorial(len(items) - 1),
            factorial(len(items) - group_size) * factorial(group_size - 1)
        )
        assert _r == 0
        for group in it.islice(it.combinations(items, group_size), count):
            remainder = [x for x in items if x not in group]  # maintain order
            yield from (
                (group, *others)
                for others in group_items(remainder, group_size)
            )


result = list(group_items(range(12), 4))
print(len(result))

from pprint import pprint
pprint(result[:3])
pprint(result[-3:])

請注意,上面的示例使用remainder = [x for x in items if x not in group]來計算哪些項目應該進入下一個遞歸級別。 如果您的組規模很大,這可能效率低下。 相反,您也可以使用一個set (如果您的項目是可散列的)。 此外,如果您的項目之間的相等比較 ( == ) 成本很高,則最好使用索引而不是項目,並根據這些索引計算groupremainder 為了簡單起見,我沒有在上面的代碼片段中包含這些方面,但是如果您對細節感興趣,我可以擴展我的答案。

不確定是否有更智能或更簡潔的方法,但您可以創建一個遞歸函數來為第一個列表選擇combinations ,然后從尚未使用的項目中選擇組合。 此外,如果子列表中的項目和子列表本身的順序似乎並不重要,這意味着第一個子列表將始終以最小元素開頭(否則它不會是第一個子列表),第二個以最小的剩余項目等。這應該減少組合的數量並防止出現任何重復的結果。

from itertools import combinations

def split(items, b, c):
    assert len(items) == b * c
    def _inner(remaining, groups):
        if len(groups) == b:
            yield groups
        else:
            first, *rest = (x for x in remaining if not groups or x not in groups[-1])
            for comb in combinations(rest, c-1):
                yield from _inner(rest, groups + [{first, *comb}])
    return _inner(items, [])

for x in split(list(range(6)), 2, 3):
    print(x)

示例輸出(使用集合列表,但您可以在生成之前將子列表轉換為列表):

[{0, 1, 2}, {3, 4, 5}]
[{0, 1, 3}, {2, 4, 5}]
[{0, 1, 4}, {2, 3, 5}]
[{0, 1, 5}, {2, 3, 4}]
[{0, 2, 3}, {1, 4, 5}]
[{0, 2, 4}, {1, 3, 5}]
[{0, 2, 5}, {1, 3, 4}]
[{0, 3, 4}, {1, 2, 5}]
[{0, 3, 5}, {1, 2, 4}]
[{0, 4, 5}, {1, 2, 3}]

對於 (a,b,c) = (12, 3, 4),它產生 5775 個元素,正如預期的那樣。 但是,對於更長的列表,這仍然需要很多時間。

使用more-itertools包中的set_partitions()函數:

# pip install more-itertools
from more_itertools import set_partitions
a, b, c = 12, 3, 4

results = []
for part in set_partitions(range(a), b):
    if all([len(p) == c for p in part]):
        results.append(part)

print(len(results))  # 5775

部分 5775 結果:

...
[[2, 4, 5, 9], [0, 1, 7, 10], [3, 6, 8, 11]]
[[1, 4, 5, 9], [0, 2, 7, 10], [3, 6, 8, 11]]
[[0, 4, 5, 9], [1, 2, 7, 10], [3, 6, 8, 11]]
[[2, 3, 5, 9], [1, 4, 7, 10], [0, 6, 8, 11]]
[[2, 3, 5, 9], [0, 4, 7, 10], [1, 6, 8, 11]]
...

如果您想知道它的作用,基本上set_partitions(range(4), 2)將 [0, 1, 2, 3] 的集合分區分為兩部分:

[[0], [1, 2, 3]], 
[[0, 1], [2, 3]], 
[[1], [0, 2, 3]], 
[[0, 1, 2], [3]], 
[[1, 2], [0, 3]], 
[[0, 2], [1, 3]], 
[[2], [0, 1, 3]]

暫無
暫無

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

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