我一直在寻找一个计算multinomial coefficients的Python库函数。
我在任何标准库中都找不到任何此类函数。 对于二项式系数(其中多项式系数是一般化),有scipy.special.binom和scipy.misc.comb。此外,numpy.random.multinomial从多项分布中提取样本,sympy.ntheory.multinomial.multinomial_coefficients返回与多项系数相关的字典。
但是,我可以不找到适当的多项系数函数,给定 a,b,...,z 返回(a + b + .. 。+ z)!/(a!b!... z!)。我错过了吗?有没有可用的充分理由?
我很乐意为SciPy提供有效的实施方案。 (我必须弄清楚如何贡献,因为我从未这样做过。)
对于背景,它们在扩展(a + b + ... + z)^ n时会出现。此外,它们会计算存放 a + b + ... +的方式z 将不同的对象分成不同的bin,这样第一个bin包含 a 对象等。我偶尔需要它们来解决Project Euler问题。
BTW,其他语言确实提供此功能:Mathematica,MATLAB,Maple。
答案 0 :(得分:4)
要部分回答我自己的问题,这是我对多项功能的简单而有效的实现:
def multinomial(lst):
res, i = 1, 1
for a in lst:
for j in range(1,a+1):
res *= i
res //= j
i += 1
return res
到目前为止,从评论中可以看出,在任何标准库中都没有有效的函数实现。由于这是一个不便,我将尝试将我的代码贡献给SymPy(不是SciPy,因为他们的scipy.special.binom实现返回一个浮点数,而不是一个整数,我不喜欢整数值函数)。
答案 1 :(得分:3)
不,Python中没有内置的多项库或函数。
无论如何这次数学可以帮助你。实际上是一种计算多项式的简单方法
关注性能是通过使用多项式系数的特征作为二项式系数的乘积来重写它:
当然在哪里
from scipy.special import binom
def multinomial(params):
if len(params) == 1:
return 1
return binom(sum(params), params[-1]) * multinomial(params[:-1])
其中params = [n1, n2, ..., nk]
。
注意:将多项式拆分为二项式的乘积也可以防止溢出。
答案 2 :(得分:1)
您写了" sympy.ntheory.multinomial.multinomial_coefficients
返回与多项系数相关的字典" ,但如果您知道如何从中提取特定系数,则该评论不清楚那本字典。使用维基百科链接中的符号,SymPy函数为您提供所有给定 m 和 n 的多项系数。如果您只想要一个特定的系数,只需将其从字典中删除:
In [39]: from sympy import ntheory
In [40]: def sympy_multinomial(params):
...: m = len(params)
...: n = sum(params)
...: return ntheory.multinomial_coefficients(m, n)[tuple(params)]
...:
In [41]: sympy_multinomial([1, 2, 3])
Out[41]: 60
In [42]: sympy_multinomial([10, 20, 30])
Out[42]: 3553261127084984957001360
Busy Beaver根据scipy.special.binom
给出了答案。该实现的潜在问题是binom(n, k)
返回浮点值。如果系数足够大,那就不一定了,所以它可能无法帮助你解决Project Euler问题。您可以使用参数binom
scipy.special.comb
而不是exact=True
。这是Busy Beaver的功能,修改为使用comb
:
In [46]: from scipy.special import comb
In [47]: def scipy_multinomial(params):
...: if len(params) == 1:
...: return 1
...: coeff = (comb(sum(params), params[-1], exact=True) *
...: scipy_multinomial(params[:-1]))
...: return coeff
...:
In [48]: scipy_multinomial([1, 2, 3])
Out[48]: 60
In [49]: scipy_multinomial([10, 20, 30])
Out[49]: 3553261127084984957001360
答案 3 :(得分:0)
我相信您可以使用向量化代码(而不是for
循环)定义一个函数以在一行中返回多项式系数,如下所示:
from scipy.special import factorial
def multinomial_coeff(c): return factorial(c.sum()) / factorial(c).prod()
(其中c
是np.ndarray
,其中包含每个不同对象的计数数量)。用法示例:
>>> coeffs = np.array([2, 3, 4])
>>> multinomial_coeff(coeffs)
1260.0
在某些情况下,这可能会比较慢,因为您将多次计算某些阶乘表达式,而在其他情况下,这可能会更快,因为我相信numpy可以自然并行化矢量化代码。同样,这减少了程序中所需的行数,并且可以说更具可读性。如果有人有时间对这些不同的选项进行速度测试,那么我很想看看结果。
答案 4 :(得分:0)
从Python 3.8
开始,
math.comb
函数(二项式系数)我们可以在没有外部库的情况下实现它:
import math
def multinomial(*params):
n = sum(params)
return math.prod(math.comb(sum(params[:i]), x) for i, x in enumerate(params, 1))
multinomial(10, 20, 30) # 3553261127084984957001360
答案 5 :(得分:0)
您自己的答案(被接受的答案)非常好,并且特别简单。但是,它确实有一个很低的效率:您的外循环for a in lst
的执行时间比必要时间多。在该循环的第一遍中,i
和j
的值始终相同,因此乘法和除法不执行任何操作。在您的示例multinomial([123, 134, 145])
中,有123
不需要的乘法和除法,这会增加代码的时间。
我建议在参数中找到最大值并将其删除,以便不要执行那些不需要的操作。这增加了代码的复杂性,但减少了执行时间,尤其是对于大数目的短列表。我下面的代码在multcoeff(123, 134, 145)
微秒内执行111
,而您的代码则花费141
微秒。这不是一个很大的增长,但这可能很重要。这是我的代码。这也将单个值而不是列表作为参数,因此与您的代码还有另一个区别。
def multcoeff(*args):
"""Return the multinomial coefficient
(n1 + n2 + ...)! / n1! / n2! / ..."""
if not args: # no parameters
return 1
# Find and store the index of the largest parameter so we can skip
# it (for efficiency)
skipndx = args.index(max(args))
newargs = args[:skipndx] + args[skipndx + 1:]
result = 1
num = args[skipndx] + 1 # a factor in the numerator
for n in newargs:
for den in range(1, n + 1): # a factor in the denominator
result = result * num // den
num += 1
return result