【问题标题】:Does Python have a function which computes multinomial coefficients?Python 有计算多项式系数的函数吗?
【发布时间】:2022-03-02 22:35:35
【问题描述】:

我正在寻找一个计算 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 问题。

顺便说一句,其他语言确实提供此功能:MathematicaMATLABMaple

【问题讨论】:

  • 因为这是我的第一个问题,我很想知道为什么这个问题被否决了。我的搜索没有回答我的问题。另外,我提供了一些背景资料。提前感谢您的任何澄清。
  • 要求我们推荐或查找书籍、工具、软件库、教程或其他场外资源的问题对于 Stack Overflow 来说是无关紧要的,因为它们往往会吸引固执己见的答案和垃圾邮件。 更多信息:What topics can I ask about here?
  • 请帮助我理解,这样一个具体的技术问题如何吸引固执己见的答案?要么该功能可用,但可能隐藏得很好或使用了不寻常的名称,或者库设计者选择不实现它有充分的理由,或者它只是一个空白(我很乐意填补)。请注意,我不是要求推荐。
  • 这样的问题在 SO 中是题外话。我们是程序员,我们也是人,我们选择库是出于特定的原因,因为我们感觉很舒服,也就是说,可能有 n 个库,我们中的任何人都会出于某种原因喜欢一些是客观的或不客观的。所以出于这个原因,SO认为它是题外话。我建议您更改您的问题并假设它不存在,也许它存在,并显示您尝试过的内容,并且如果已经有解决方案,社区中的某个人肯定会用函数名称做出回应,或者提出一些替代方案.
  • 这正是我所做的,这就是我感到困惑的原因。

标签: python scipy combinatorics


【解决方案1】:

为了部分回答我自己的问题,这是我对多项式函数的简单且相当有效的实现:

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

到目前为止,从 cmets 看来,任何标准库中都不存在该函数的有效实现。

更新(2020 年 1 月)。 正如 Don Hatch 在 cmets 中指出的那样,这可以通过寻找最大的参数来进一步改进(尤其是在它支配所有其他参数的情况下):

def multinomial(lst):
    res, i = 1, sum(lst)
    i0 = lst.index(max(lst))
    for a in lst[:i0] + lst[i0+1:]:
        for j in range(1,a+1):
            res *= i
            res //= j
            i -= 1
    return res

【讨论】:

  • 根据我的经验,使用sum()math.factorial() 计算公式会更快。
  • 这当然取决于你想要做什么。你不会用这种方法解决任何(3 位数)欧拉项目问题
  • 嗨 Reiner,在我意识到它实际上不是很好之前,我不小心点了你的答案。 multinomial([10**7,2]) 需要 0.8 秒。 multinomial([10**8,2]) 需要 8 秒。 multinomial([10**9,2]) 使我的机器崩溃(python2)或需要 80 秒(python3)。所有这些都可能是即时的。
  • 不过,您可以很容易地修复它。一种方法如下:将函数中的前两行替换为res = 1i = sum(lst); index_of_max = lst.index(max(lst)); for a in lst[:index_of_max] + lst[index_of_max+1:]:,然后将i+=1 替换为i-=1
  • 其实,琐碎的multinomial([10**8,1]) 甚至更琐碎的multinomial([10**8,0]) 也说明了你的代码的问题。
【解决方案2】:

不,Python 中没有内置的多项式库或函数。

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

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

当然在哪里

感谢scipy.special.binom 和递归的魔力,您可以像这样解决问题:

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].

注意:将多项式拆分为二项式的乘积通常也可以很好地防止溢出。

【讨论】:

  • 谢谢,这基本上就是我所做的(将递归展开为循环以提高性能)。然而,这不是我的问题;相反:将它包含在 SciPy 中会是一个好主意吗(如果它还没有,它似乎不是),因为它存在于其他几种语言中?相信我,我不需要数学课。
  • 这真是一个要问scipy 维护者的问题。
  • 好的,好的,会的。这可能是错误的观众。谢谢。
  • 也就是说,我不确定除了 SciPy 之外是否还有其他库。
  • 这里是一年多后的更新。对于我的解决问题的编程(比如 Project Euler),我同时完全切换到 Julia,在我看来它更适合这样的东西。顺便说一句,正如您所料,多项式函数包含在 Julia 的 Combinatorics 库中。
【解决方案3】:

您写了 sympy.ntheory.multinomial.multinomial_coefficients 返回与多项式系数相关的字典”,但从该评论中不清楚您是否知道如何从该字典中提取特定系数。使用维基百科链接中的符号,SymPy 函数为您提供 all 给定 mn 的多项式系数。如果您只想要一个特定的系数,只需将其从字典中拉出即可:

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

忙碌的海狸给出了一个用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

【讨论】:

  • 谢谢,我确实没有研究如何从该字典中获取特定系数。当我只想计算一个值时,生成一个完整的字典让我感到难以置信的浪费,所以我为一些更大的输入做了一个快速的计时。 sympy_multinomial([123,134,145]) 例如占用我自己简单函数的 1000 倍以上。
【解决方案4】:

这里有两种方法,一种使用阶乘,一种使用Stirling's approximation

使用阶乘

您可以使用矢量化代码(而不是 for-loops)定义一个函数以在一行中返回多项式系数,如下所示:

from scipy.special import factorial

def multinomial_coeff(c):
    return factorial(c.sum()) / factorial(c).prod()

(其中c 是一个np.ndarray,包含每个不同对象的计数)。使用示例:

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

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

使用Stirling's approximation

事实上,多项式系数的对数计算起来更快(基于Stirling's approximation)并且允许计算更大的系数:

from scipy.special import gammaln

def log_multinomial_coeff(c):
    return gammaln(c.sum()+1) - gammaln(c+1).sum()

使用示例:

>>> import numpy as np
>>> coeffs = np.array([2, 3, 4])
>>> np.exp(log_multinomial_coeff(coeffs))
1259.999999999999

【讨论】:

  • 事实上,多项式系数的对数计算速度更快(基于斯特林近似)并且允许计算更大的系数:from scipy.special import gammalndef log_multinomial_coeff(c): return gammaln(c.sum()+1) - gammaln(c+1).sum()
  • 我已经更新了答案以包含之前的评论
【解决方案5】:

您自己的答案(被接受的)非常好,而且特别简单。但是,它确实有一个显着的低效率:您的外部循环 for a in lst 执行的次数超出了必要的次数。在第一次通过该循环时,ij 的值始终相同,因此乘法和除法没有任何作用。在您的示例 multinomial([123, 134, 145]) 中,有 123 不需要的乘法和除法,增加了代码的时间。

我建议在参数中找到最大值并去掉它,这样那些不需要的操作就不用做了。这增加了代码的复杂性,但减少了执行时间,尤其是对于大数字的短列表。我下面的代码在111 微秒内执行multcoeff(123, 134, 145),而您的代码需要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

【讨论】:

    【解决方案6】:

    开始Python 3.8

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

    import math
    
    def multinomial(*params):
      return math.prod(math.comb(sum(params[:i]), x) for i, x in enumerate(params, 1))
    
    multinomial(10, 20, 30) # 3553261127084984957001360
    

    【讨论】:

    • 如果性能是一个问题,不幸的是,这有点低效,因为许多术语取消了这种方法没有利用的优势。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-22
    • 1970-01-01
    • 2014-05-22
    • 1970-01-01
    相关资源
    最近更新 更多