Python有一个计算多项系数的函数吗?

时间:2017-09-22 22:46:05

标签: python scipy combinatorics

我一直在寻找一个计算multinomial coefficients的Python库函数。

我在任何标准库中都找不到任何此类函数。 对于二项式系数(其中多项式系数是一般化),有scipy.special.binomscipy.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,其他语言确实提供此功能:MathematicaMATLABMaple

6 个答案:

答案 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中没有内置的多项库或函数。

无论如何这次数学可以帮助你。实际上是一种计算多项式的简单方法

sum

关注性能是通过使用多项式系数的特征作为二项式系数的乘积来重写它:

当然在哪里

感谢以及递归的魔力,你可以解决这个问题:

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()

(其中cnp.ndarray,其中包含每个不同对象的计数数量)。用法示例:

>>> coeffs = np.array([2, 3, 4])
>>> multinomial_coeff(coeffs)
1260.0

在某些情况下,这可能会比较慢,因为您将多次计算某些阶乘表达式,而在其他情况下,这可能会更快,因为我相信numpy可以自然并行化矢量化代码。同样,这减少了程序中所需的行数,并且可以说更具可读性。如果有人有时间对这些不同的选项进行速度测试,那么我很想看看结果。

答案 4 :(得分:0)

Python 3.8开始,

我们可以在没有外部库的情况下实现它:

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的执行时间比必要时间多。在该循环的第一遍中,ij的值始终相同,因此乘法和除法不执行任何操作。在您的示例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