如何将元素放入几个桶中(可能使用 itertools.combinations())?

How to put elements into few buckets (probably with itertools.combinations())?

提问人:MicroElf 提问时间:10/21/2023 最后编辑:MicroElf 更新时间:10/21/2023 访问量:91

问:

我需要遍历所有可能的组合,将初始可迭代的元素放入多个桶中。
插图:放入两个长度和
ABCDEFG32 -->

[ABC, DE], [ABC, DF], [ABC, DG], [ABD, CE], [ABD, EF], ..., [CDE, FG]

没有重复,每个桶中只有唯一的组合(没有排列)。

编辑:存储桶很重要,因为并被视为不同的组合(总体上仍然是相同的 5 个元素,但在存储桶中的分布不同)。而和被认为是相同的组合。
存储桶中的顺序更改不是不同的组合。在铲斗之间移动元素是不同的组合。
ABC, DEABD, CEABC, DEACB, DE

我想我应该以某种方式使用 itertools.combinations():

itertools.combinations('ABCDEFG', 3) --> ABC, ABD, ..., EFG

或将其与某些东西结合起来。

但我正在努力弄清楚如何进一步将输出分布在多个不同长度的存储桶上。

组合 python-itertools

评论

1赞 Tim Roberts 10/21/2023
桶大多无关紧要。只需选择长度为 5 的所有组合即可。使用前 3 个和后 2 个,
1赞 Brian61354270 10/21/2023
是否和被认为是不同的?[ABC, DE][ABE, DC]
0赞 MicroElf 10/21/2023
@Brian61354270 是的,这就是重点。我已经编辑了我的问题并进行了澄清。
0赞 Kelly Bundy 10/21/2023
@TimRoberts 不会得到所有(当你发表评论时不清楚)。
1赞 Brian61354270 10/21/2023
@MicroElf 我在我的答案中添加了一个用于处理任意数量的垃圾箱的变体(根据您的旧评论)

答:

2赞 Brian61354270 10/21/2023 #1

以下是依赖于“样本空间”元素可散列的可能实现。这假设元素属于哪个桶很重要,如此特殊,并被视为两种不同的结果。[ABC, DE][ABD, CE]

import itertools
from typing import Iterable, Iterator, TypeVar


_T = TypeVar("_T")


def two_bin_combs(
    iterable: Iterable[_T], b1: int, b2: int
) -> Iterator[tuple[tuple[_T, ...], tuple[_T, ...]]]:
    space = set(iterable)
    for left in itertools.combinations(space, b1):
        for right in itertools.combinations(space - set(left), b2):
            yield left, right

然后

sample_space = "ABCDEFG"
buckets = list(two_bin_combs(sample_space, 3, 2))

print(len(buckets))
print(buckets)

输出

210
[
    (('A', 'B', 'C'), ('E', 'D')),
    (('A', 'B', 'D'), ('C', 'E')),
    (('A', 'B', 'E'), ('C', 'D')), 
    (('A', 'C', 'D'), ('E', 'B')), 
    (('A', 'C', 'E'), ('D', 'B'))
    # ...
    (('C', 'F', 'G'), ('D', 'E')), 
    (('D', 'E', 'F'), ('G', 'C')), 
    (('D', 'E', 'G'), ('F', 'C')), 
    (('D', 'F', 'G'), ('C', 'E')), 
    (('E', 'F', 'G'), ('D', 'C')),
]

请注意,输出顺序不是确定性的,因为它取决于设置的迭代顺序,该顺序可能因运行而异


这种方法也可以推广到任意数量的 bing:

def bin_combs(
    iterable: Iterable[_T], *bin_sizes: int
) -> Iterator[tuple[tuple[_T, ...], ...]]:

    total_space = frozenset(iterable)
    if len(total_space) < sum(bin_sizes):
        raise ValueError("total size of bins exceeds size of sample space")

    def _bin_combs(
        space: frozenset[_T], *bin_sizes: int
    ) -> Iterator[tuple[tuple[_T, ...], ...]]:

        if not bin_sizes:
            yield ()
            return

        curr_size, *remaining = bin_sizes

        for head in itertools.combinations(space, curr_size):
            for tail in _bin_combs(space - frozenset(head), *remaining):
                yield (head,) + tail

    return _bin_combs(total_space, *bin_sizes)

评论

0赞 Kelly Bundy 10/21/2023
你有充分的理由而不是吗?yield fromreturn
0赞 Brian61354270 10/21/2023
@KellyBundy 不,我更喜欢。编辑return
1赞 Kelly Bundy 10/21/2023
有时是需要的,但几乎我总是看到它,它只是添加了一个不必要的生成器迭代器包装器,减慢了速度。
1赞 Kelly Bundy 10/21/2023
哦,我的意思是,有时在“类似”的情况下,一个函数创建了一些迭代器,然后只执行该迭代器。在这种情况下,通常更好,这就是我的意思。(例外情况是迭代器会关闭,就像不起作用一样,并且 a 保持活动状态。yield fromreturnwith open(filename) as f: return fyield from