A盒中N球组合的枚举?

时间:2009-06-15 13:06:23

标签: python enumeration combinations

我想在 框中列举 N 球的所有可能组合。

例如: 我有 8 球来处理 3 框:

         box_1   box_2   box_3
case-1       8       0       0
case-2       0       8       0
case-3       0       0       8 
case-4       7       1       0
case-5       7       0       1
case-6       6       2       0
...

我的第一个问题是我需要 A 循环来执行此操作,但我希望 A 和< strong> N 是用户的输入。那么如何在不编写用户可能需要的所有可能数量的循环的情况下如何做呢?

N 的值介于2到800之间,所以它的要求非常高计算时间如此。如何优化该算法?

如果你使用python语言回答我,我将不胜感激。 感谢所有的贡献!

8 个答案:

答案 0 :(得分:6)

这可以从python 2.6开始,(2.5-friendly implementation of itertools.permutations is available as well):

>>> import itertools
>>> boxes = 3
>>> balls = 8
>>> rng = list(range(balls + 1)) * boxes
>>> set(i for i in itertools.permutations(rng, boxes) if sum(i) == balls)
{(0, 1, 7), (3, 1, 4), (0, 4, 4), (1, 0, 7), (4, 0, 4), (3, 0, 5), (1, 2, 5), (1, 7, 0), (0, 8, 0), (1, 4, 3), (6, 0, 2), (4, 3, 1), (3, 3, 2), (0, 5, 3), (5, 3, 0), (5, 1, 2), (2, 4, 2), (4, 4, 0), (3, 2, 3), (7, 1, 0), (5, 2, 1), (0, 6, 2), (6, 1, 1), (2, 2, 4), (1, 1, 6), (0, 2, 6), (7, 0, 1), (2, 1, 5), (0, 0, 8), (2, 0, 6), (2, 6, 0), (5, 0, 3), (2, 5, 1), (1, 6, 1), (8, 0, 0), (4, 1, 3), (6, 2, 0), (3, 5, 0), (0, 3, 5), (4, 2, 2), (1, 3, 4), (0, 7, 1), (1, 5, 2), (2, 3, 3), (3, 4, 1)}

答案 1 :(得分:5)

伪代码:

Enumerate(Balls, Boxes)
  if Boxes<=0 
    Error
  elseif Boxes=1 
    Box[1] = Balls
    PrintBoxes
  else
    forall b in 0..Balls 
      Box[Boxes] = b
      Enumerate(Balls-b, Boxes-1)
    endfor
  endif
end

解释

从第一个框开始,如果没有框,则抱怨并退出。 如果它是要填充的最后一个框,则丢弃所有剩余的球并显示结果。 如果有更多的盒子,首先添加0个球并与其他盒子重复该过程。然后加1球2球,直到没有球为止。

为表明该算法有效,我给出了一个实际值,3个球和2个盒子的例子。

我们有一个名为Box的盒子数组,每个盒子可以容纳任意数量的球(值)。 PrintBoxes打印框的当前值。

Box = (0,0)
Enumerate(3, 2)
  b=0
  Box = (0,0)
  Enumerate(3,1)
    Box = (3,0) 
    Print!
  b=1 
  Box = (0,1)
  Enumerate(2,1)
    Box = (2,1)
    Print!
  b=2
  Box = (0,2)
  Enumerate(1,1)
    Box = (1,2)
    Print!
  b=3   
  Box = (0,3)
  Enumerate(0,1)
    Box = (0,3)
    Print!

 Output:

 (3,0)
 (2,1)
 (1,2)
 (0,3)

 Which are all the combinations.

另一个有3个盒子和3个球的例子:

Box = (0,0,0)
Enumerate(3, 3)
  b=0
  Box = (0,0,0)
  Enumerate(3,2)
    b=0
    Box = (0,0,0)
    Enumerate(3,1)
      Box = (3,0,0)
    b=1
    Box = (0,1,0)
    Enumerate(2,1)
      Box = (2,1,0)
    b=2
    Box = (0,2,0)
    Enumerate(1,1)
      Box = (1,2,0)
    b=3
    Box = (0,3,0)
    Enumerate(0,1)
      Box = (0,3,0)
  b=1 
  Box = (0,0,1)
  Enumerate(2,2)
    b=0
    Box = (0,0,1)
    Enumerate(2,1)
      Box = (2,0,1)
    b=1
    Box = (0,1,1)
    Enumerate(1,1)
      Box = (1,1,1)
    b=2
    Box = (0,2,1)
    Enumerate(0,1)
      Box = (0,2,1)
  b=2
  Box = (0,0,2)
  Enumerate(1,2)
    b=0
    Box = (0,0,2)
    Enumerate(1,1)
      Box = (1,0,2)
    b=1
    Box = (0,1,2)
    Enumerate(0,1)
      Box = (0,1,2)
  b=3   
  Box = (0,0,3)
  Enumerate(0,2)
    b=0
    Box = (0,0,3)
    Enumerate(0,1)
      Box = (0,0,3)

Output
(3,0,0)
(2,1,0)
(1,2,0)
(0,3,0)
(2,0,1)
(1,1,1)
(0,2,1)
(1,0,2)
(0,1,2)
(0,0,3)

答案 2 :(得分:2)

请参阅3.1中的itertools.combinations_with_replacement以获取用python编写的示例。此外,在组合系统中,将组合替换问题转换为通常的无替换组合问题是常见的,这种问题已经内置在2.6 itertools中。这具有不产生丢弃元组的优点,例如基于产品或排列的解决方案。以下是使用标准(n,r)术语的示例,在您的示例中为(A,N)。

import itertools, operator
def combinations_with_replacement_counts(n, r):
    size = n + r - 1
    for indices in itertools.combinations(range(size), n-1):
        starts = [0] + [index+1 for index in indices]
        stops = indices + (size,)
        yield tuple(map(operator.sub, stops, starts))

>>> list(combinations_with_replacement_counts(3, 8))
[(0, 0, 8), (0, 1, 7), (0, 2, 6), (0, 3, 5), (0, 4, 4), (0, 5, 3), (0, 6, 2), (0, 7, 1), (0, 8, 0), (1, 0, 7), (1, 1, 6), (1, 2, 5), (1, 3, 4), (1, 4, 3), (1, 5, 2), (1, 6, 1), (1, 7, 0), (2, 0, 6), (2, 1, 5), (2, 2, 4), (2, 3, 3), (2, 4, 2), (2, 5, 1), (2, 6, 0), (3, 0, 5), (3, 1, 4), (3, 2, 3), (3, 3, 2), (3, 4, 1), (3, 5, 0), (4, 0, 4), (4, 1, 3), (4, 2, 2), (4, 3, 1), (4, 4, 0), (5, 0, 3), (5, 1, 2), (5, 2, 1), (5, 3, 0), (6, 0, 2), (6, 1, 1), (6, 2, 0), (7, 0, 1), (7, 1, 0), (8, 0, 0)]

答案 3 :(得分:1)

您可以定义一个递归generator,为您希望嵌套的每个'for循环'创建一个子生成器,如下所示:

def ballsAndBoxes(balls, boxes, boxIndex=0, sumThusFar=0):
    if boxIndex < (boxes - 1):
        for counter in xrange(balls + 1 - sumThusFar):
            for rest in ballsAndBoxes(balls, boxes,
                                      boxIndex + 1,
                                      sumThusFar + counter):
                yield (counter,) + rest
    else:
        yield (balls - sumThusFar,)

当你在顶层调用它时,它只需要一个'ball'和'boxes'参数,其他参数作为默认值,这样递归调用可以传递不同的东西。它将产生作为你的值的整数元组(长度为'box')。

要获得您在此帖子顶部指定的确切格式,您可以将其命名为:

BALLS = 8
BOXES = 3
print '\t',
for box in xrange(1, BOXES + 1):
    print '\tbox_%d' % (box,),
print
for position, value in enumerate(ballsAndBoxes(BALLS, BOXES)):
    print 'case-%d\t\t%s' % (position + 1, 
                             "\t".join((str(v) for v in value)))

答案 4 :(得分:1)

使用python生成器是个好主意,就像上面所做的那样,但这里更直接(不确定效率)版本:

def balls_in_baskets(balls=1, baskets=1):
    if baskets == 1:
        yield [balls]
    elif balls == 0:
        yield [0]*baskets
    else:
        for i in xrange(balls+1):
            for j in balls_in_baskets(balls-i, 1):
                for k in balls_in_baskets(i, baskets-1):
                    yield j+k

for i in balls_in_baskets(8,3):
    print i

答案 5 :(得分:0)

如果你想使用你自己的功能,Gamecat的答案可能有效 其他 使用http://probstat.sourceforge.net/,它非常快(用c写)

或python 2.6中的itertools

答案 6 :(得分:0)

如果您只是想知道可能性的数量,而不是列出它们,那么以下公式将起作用:

  

可能性=(N + A-1)C N =(N + A-1)!/(N!x(A-1)!)

其中aCb(a选择b)是从一组大小a中选择大小b的组合的方式的数量。

!表示阶乘,即5!= 5 * 4 * 3 * 2 * 1,n!= n *(n-1)*(n-2)* ... * 3 * 2 * 1。对不起,如果我教你如何吮吸鸡蛋。

在python中:

from math import factorial as f
balls=N
boxes=A
def p(balls,boxes):
    return f(balls+boxes-1)/f(balls)/f(boxes-1)
p(3,2)
  4
p(3,3)
  10

与Gamecat的例子一致。

为了解释公式的工作原理,让我们来看看五个球和三个方框。将球表示为星号。我们想要放置3-1 = 2个分界线,将球分成3个隔间。

例如,我们可以

* | * | *   *   *        (1,1,3)
*   * | *   *   * |      (2,3,0)
*   *   *   *   * |  |   (5,0,0)

7个符号可以7种方式订购!= 5040种可能的方式。由于所有的球是相同的,我们不担心球的顺序,所以我们除以5!同样,我们不担心分界线的顺序,所以我们除以2!这给了我们7C5 = 7!/(5!* 2!)= 21种可能性。

关于Combinations的维基百科文章有一节“重复组合的数量”,这是一个更美味的方式重新计算的计数组合问题(甜甜圈和水果而不是球)。

如果要列出组合,请注意数字增长的速度。对于20个球和9个盒子,有超过300万种可能性!

编辑:我之前的回答将此问题与整数分区进行了比较,以显示可能性增加的速度。我的新答案与原始问题更相关。

答案 7 :(得分:0)

def iterate_assignments(N, K):
    buckets = [0] * K
    buckets[0] = K
    while True:
        yield buckets
        if buckets[-1] == N:
            return
        non_empty_buckets = sorted([i for i, count in enumerate(buckets) if count > 0])
        if non_empty_buckets[-1] == K - 1:
            temp = buckets[-1]
            buckets[-1] = 0
            buckets[non_empty_buckets[-2] + 1] = temp + 1
            buckets[non_empty_buckets[-2]] -= 1
        else:
            buckets[non_empty_buckets[-1]] -= 1
            buckets[non_empty_buckets[-1] + 1] += 1

这是python中的一个解决方案,可以有效地将N个球的所有可能分配给O(K)内存中的K个桶和O(#个可能的分配)时间。

从一个任务开始,所有球都位于​​最左边的桶中(为了便于理解,假设所有桶都是从左到右排列)。通过以下列方式逐步向右移动球来生成所有后续分配:

(1)如果最右边的球在最右边的球桶中,找到下一个最右边的球并将这两个球移动到下一个最右边球的右边一个球桶中 (2)如果最右边的球在其他任何地方,请向右移动一个铲斗

从这个方案中,清楚地看出为什么这只会产生独特的组合。需要更多考虑才能了解为什么会产生所有唯一组合。如果人们有兴趣,我可以尝试将证明形式化,但我现在正在跳过:)