【问题标题】:Producing all groups of fixed-length combinations生成所有组的固定长度组合
【发布时间】:2013-01-30 01:16:05
【问题描述】:

我正在寻找一种算法和/或 Python 代码来生成将一组 n 元素划分为零组或多组 r 元素和余数的所有可能方法。例如,给定一个集合:

[1,2,3,4,5]

n = 5r = 2,我想得到

((1,2,3,4,5),)
((1,2),(3,4,5))
((1,3),(2,4,5))
...
((1,2),(3,4),(5,))
((1,2),(3,5),(4,))
...

也就是说,从集合中抽取0组2项的结果,加上从集合中抽取1组2项的结果,再加上从集合中抽取2组2项的结果,...如果n 更大,这将继续。

生成这些结果的顺序并不重要,每个单独组中元素的顺序也不重要,结果中组的顺序也不重要。 (例如((1,3),(2,4,5)) 等价于((3,1),(4,5,2))((2,5,4),(1,3)) 等等。)我正在寻找的是每个不同的结果至少产生一次,最好是一次,以尽可能有效的方式.


蛮力方法是从n元素中生成r的所有可能组合,然后创建任意数量的这些组合的所有可能组(powerset),迭代它们并只处理组中的组合没有共同元素的组合。即使是少量元素,这也花费了 far 太长的时间(它需要迭代 2^(n!/r!(n-r)!) 个组,因此复杂度是双指数的)。

基于this question 中给出的代码,这基本上是r = 2n 的特例,我想出了以下内容:

def distinct_combination_groups(iterable, r):
    tpl = tuple(iterable)
    yield (tpl,)
    if len(tpl) > r:
        for c in combinations(tpl, r):
            for g in distinct_combination_groups(set(tpl) - set(c), r):
                yield ((c,) + g)

这似乎会产生所有可能的结果,但它包含一些重复项,当n 相当大时,它们的数量是不平凡的。所以我希望有一种算法可以避免重复。

【问题讨论】:

  • 这里math.stackexchange.com/questions/222780/…,在答案中,您可以找到Knuth 的《计算机编程的艺术》第4 卷分册3b 的链接
  • @Teudimundo:您的链接是关于将 n 元素的分区找到恰好 k 集的问题,任何大小。但是这里的 OP 希望将 n 元素划分为任意数量的大小正好 r 的集合(可能还有一些余数)。

标签: python algorithm


【解决方案1】:

这个怎么样?

from itertools import combinations

def partitions(s, r):
    """
    Generate partitions of the iterable `s` into subsets of size `r`.

    >>> list(partitions(set(range(4)), 2))
    [((0, 1), (2, 3)), ((0, 2), (1, 3)), ((0, 3), (1, 2))]
    """
    s = set(s)
    assert(len(s) % r == 0)
    if len(s) == 0:
        yield ()
        return
    first = next(iter(s))
    rest = s.difference((first,))
    for c in combinations(rest, r - 1):
        first_subset = (first,) + c
        for p in partitions(rest.difference(c), r):
            yield (first_subset,) + p

def partitions_with_remainder(s, r):
    """
    Generate partitions of the iterable `s` into subsets of size
    `r` plus a remainder.

    >>> list(partitions_with_remainder(range(4), 2))
    [((0, 1, 2, 3),), ((0, 1), (2, 3)), ((0, 2), (1, 3)), ((0, 3), (1, 2))]
    >>> list(partitions_with_remainder(range(3), 2))
    [((0, 1, 2),), ((1, 2), (0,)), ((0, 2), (1,)), ((0, 1), (2,))]
    """
    s = set(s)
    for n in xrange(len(s), -1, -r): # n is size of remainder.
        if n == 0:
            for p in partitions(s, r):
                yield p
        elif n != r:
            for remainder in combinations(s, n):
                for p in partitions(s.difference(remainder), r):
                    yield p + (remainder,)

来自 OP 的示例:

>>> pprint(list(partitions_with_remainder(range(1, 6), 2)))
[((1, 2, 3, 4, 5),),
 ((4, 5), (1, 2, 3)),
 ((3, 5), (1, 2, 4)),
 ((3, 4), (1, 2, 5)),
 ((2, 5), (1, 3, 4)),
 ((2, 4), (1, 3, 5)),
 ((2, 3), (1, 4, 5)),
 ((1, 5), (2, 3, 4)),
 ((1, 4), (2, 3, 5)),
 ((1, 3), (2, 4, 5)),
 ((1, 2), (3, 4, 5)),
 ((2, 3), (4, 5), (1,)),
 ((2, 4), (3, 5), (1,)),
 ((2, 5), (3, 4), (1,)),
 ((1, 3), (4, 5), (2,)),
 ((1, 4), (3, 5), (2,)),
 ((1, 5), (3, 4), (2,)),
 ((1, 2), (4, 5), (3,)),
 ((1, 4), (2, 5), (3,)),
 ((1, 5), (2, 4), (3,)),
 ((1, 2), (3, 5), (4,)),
 ((1, 3), (2, 5), (4,)),
 ((1, 5), (2, 3), (4,)),
 ((1, 2), (3, 4), (5,)),
 ((1, 3), (2, 4), (5,)),
 ((1, 4), (2, 3), (5,))]

【讨论】:

  • 那行得通 :-) 理想情况下,我也希望对算法进行解释,但从代码中很容易弄清楚。
  • @DavidZaslavsky:我觉得让你逆向工程会更有趣!
  • more_itertoolssympy 都包含许多分区函数,但我找不到一个允许像这样指定子集大小的函数。我怀疑如果你在 more_itertools 的拉取请求中提出 partitionspartitions_with_remainder,他们会接受。
  • @Stef 你知道,我不会认为这个算法通常有用到值得包含在 more_itertools 中,但至少提出它可能是一个好主意! (我当然会把它留给 Gareth,因为这不是我要分享的代码)
  • 我相信partitions(使用更好的名称)可能有一个用例,但partitions_with_remainder 没有。但无论哪种情况,都欢迎您使用我的代码并提出拉取请求,因为我在此处发布它时同意根据 CC-BY-SA 许可它。 (当然,您首先要对代码进行现代化改造——range 而不是 xrange 等等。)
猜你喜欢
  • 1970-01-01
  • 2017-02-02
  • 2020-03-11
  • 1970-01-01
  • 1970-01-01
  • 2020-08-31
  • 2013-01-04
  • 2017-07-10
  • 1970-01-01
相关资源
最近更新 更多