[英]C# split a list into all combinations of n groups - code migration from Python
[英]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 = 4
, b = 2
, c = 2
的所需輸出將是:
[[[1, 2], [3, 4]],
[[1, 3], [2, 4]],
[[1, 4], [2, 3]]]
對於a = 3
, b = 3
, c = 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
(如果您的項目是可散列的)。 此外,如果您的項目之間的相等比較 ( ==
) 成本很高,則最好使用索引而不是項目,並根據這些索引計算group
和remainder
。 為了簡單起見,我沒有在上面的代碼片段中包含這些方面,但是如果您對細節感興趣,我可以擴展我的答案。
不確定是否有更智能或更簡潔的方法,但您可以創建一個遞歸函數來為第一個列表選擇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.