【问题标题】:Generate all possible outcomes of k balls in n bins (sum of multinomial / categorical outcomes)在 n 个 bin 中生成 k 个球的所有可能结果(多项式/分类结果的总和)
【发布时间】:2016-10-09 06:31:15
【问题描述】:

假设我们有 n 投掷 k 球的垃圾箱。什么是快速(即使用 numpy/scipy 而不是 python 代码)将所有可能的结果生成为矩阵的方法?

例如,如果n = 4k = 3,我们需要以下numpy.array

3 0 0 0
2 1 0 0
2 0 1 0
2 0 0 1
1 2 0 0
1 1 1 0
1 1 0 1
1 0 2 0
1 0 1 1
1 0 0 2
0 3 0 0
0 2 1 0
0 2 0 1
0 1 2 0
0 1 1 1
0 1 0 2
0 0 3 0
0 0 2 1
0 0 1 2
0 0 0 3

如果遗漏了任何排列,我们深表歉意,但这是总体思路。生成的排列不必按任何特定顺序排列,但上面的列表便于在心理上对它们进行分类迭代。

更好的是,有没有办法将每个整数从 1 映射到 multiset number(此列表的基数)直接映射到给定的排列?

这个问题与以下问题有关,这些问题在 R 中实现,具有非常不同的设施:

还有相关参考资料:

【问题讨论】:

  • 需要按这个顺序吗?
  • @Kupiakos 不。我没有意识到,发布第一个问题的人列出了相同的列表。
  • stars and bars 的角度思考,使用“未排名组合”的算法来查找条形(或星形)的位置。此处概述了一种这样的算法:Finding the k-combination for a given number
  • 谢谢@morningsun。我会自己实现它,但最好先知道 Python 世界中是否已经存在类似的东西。

标签: python numpy scipy permutation combinatorics


【解决方案1】:

这里有一个使用itertools.combinations_with_replacement的生成器解决方案,不知道是否适合你的需求。

def partitions(n, b):
    masks = numpy.identity(b, dtype=int)
    for c in itertools.combinations_with_replacement(masks, n): 
        yield sum(c)

output = numpy.array(list(partitions(3, 4)))
# [[3 0 0 0]
#  [2 1 0 0]
#  ...
#  [0 0 1 2]
#  [0 0 0 3]]

此函数的复杂性呈指数增长,因此可行与不可行之间存在离散边界。

请注意,虽然 numpy 数组在构造时需要知道它们的大小,但这很容易实现,因为很容易找到多重集数。下面可能是一个更好的方法,我没有做计时。

from math import factorial as fact
from itertools import combinations_with_replacement as cwr

nCr = lambda n, r: fact(n) / fact(n-r) / fact(r)

def partitions(n, b):
    partition_array = numpy.empty((nCr(n+b-1, b-1), b), dtype=int)
    masks = numpy.identity(b, dtype=int)
    for i, c in enumerate(cwr(masks, n)): 
        partition_array[i,:] = sum(c)
    return partition_array

【讨论】:

【解决方案2】:

这是一个带有列表推导的幼稚实现,与 numpy 相比不确定性能

def gen(n,k):
    if(k==1):
        return [[n]]
    if(n==0):
        return [[0]*k]
    return [ g2 for x in range(n+1) for g2 in [ u+[n-x] for u in gen(x,k-1) ] ]

> gen(3,4)
[[0, 0, 0, 3],
 [0, 0, 1, 2],
 [0, 1, 0, 2],
 [1, 0, 0, 2],
 [0, 0, 2, 1],
 [0, 1, 1, 1],
 [1, 0, 1, 1],
 [0, 2, 0, 1],
 [1, 1, 0, 1],
 [2, 0, 0, 1],
 [0, 0, 3, 0],
 [0, 1, 2, 0],
 [1, 0, 2, 0],
 [0, 2, 1, 0],
 [1, 1, 1, 0],
 [2, 0, 1, 0],
 [0, 3, 0, 0],
 [1, 2, 0, 0],
 [2, 1, 0, 0],
 [3, 0, 0, 0]]

【讨论】:

    【解决方案3】:

    出于参考目的,以下代码使用Ehrlich's algorithm 遍历 C++、Javascript 和 Python 中多重集的所有可能组合:

    https://github.com/ekg/multichoose

    可以使用this method 将其转换为上述格式。具体来说,

    for s in multichoose(k, set):
        row = np.bincount(s, minlength=len(set) + 1)
    

    这仍然不是纯 numpy,但可以用来快速填充预先分配的 numpy.array

    【讨论】:

      【解决方案4】:

      这是我为此想出的解决方案。

      import numpy, itertools
      def multinomial_combinations(n, k, max_power=None):
          """returns a list (2d numpy array) of all length k sequences of 
          non-negative integers n1, ..., nk such that n1 + ... + nk = n."""
          bar_placements = itertools.combinations(range(1, n+k), k-1)
          tmp = [(0,) + x + (n+k,) for x in bar_placements]
          sequences =  numpy.diff(tmp) - 1
          if max_power:
              return sequences[numpy.where((sequences<=max_power).all(axis=1))][::-1]
          else:
              return sequences[::-1]
      

      注意 1:末尾的 [::-1] 只是颠倒顺序以匹配您的示例输出。

      注意 2:找到这些序列相当于找到所有方法来排列 n 个星和 k-1 个条(以填充 n+k-1 个点)(参见stars and bars thm 2)。

      注意 3:max_power 参数让您可以选择仅返回所有整数都低于某个最大值的序列。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-10-08
        • 1970-01-01
        • 2015-08-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-05
        相关资源
        最近更新 更多