web-dev-qa-db-ja.com

Pythonには多項係数を計算する関数がありますか?

多項係数 を計算するPythonライブラリ関数を探していました。

標準ライブラリのいずれにもそのような関数は見つかりませんでした。二項係数(多項係数は一般化されています)には、 scipy.special.binomscipy.misc.comb があります。また、 numpy.random.multinomial は多項分布からサンプルを抽出し、 sympy.ntheory.multinomial.multinomial_coefficients は多項係数に関連する辞書を返します。

ただし、not適切な多項係数関数を見つけることができます。これにより、a、b、...、z(a + b + ... + z)!/(a!b!... z!)。見逃しましたか?利用できるものがない正当な理由はありますか?

SciPyによると、効率的な実装に貢献できれば幸いです。 (私はこれを行ったことがないので、貢献する方法を理解する必要があります)。

背景として、展開時に表示されます(a + b + ... + z)^ n。また、デポジットの方法を数えますa + b + ... + z最初のビンにaオブジェクトなどが含まれるように、個別のオブジェクトを個別のビンに分割します。プロジェクトオイラーの問題で時々必要になります。

ところで、他の言語はこの関数を提供します: Mathematica[〜#〜] matlab [〜#〜]Maple

13
Reiner Martin

私自身の質問に部分的に答えるために、多項関数の単純でかなり効率的な実装を次に示します。

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.special.binomの実装は整数ではなくfloatを返すため、SciPyではありません。整数値の関数では嫌いです)。

5
Reiner Martin

いいえ、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]

注:多項分布を二項式の積として分割することも、一般にオーバーフローを防ぐのに役立ちます。

3
Marco

"_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)が浮動小数点値を返すことです。係数が十分に大きい場合、正確ではないため、プロジェクトオイラーの問題にはおそらく役立ちません。 binomの代わりに、引数_scipy.special.comb_を指定して _exact=True_ を使用できます。これはビジービーバーの関数であり、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
_
1

開始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
0
Xavier Guihot

次のように、(for- loopsの代わりに)ベクトル化されたコードを使用して、多項係数を1行で返す関数を定義できると思います。

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がベクトル化されたコードを自然に並列化すると私が信じているために速くなる場合もあります。また、これにより、プログラムに必要な行数が減り、間違いなく読みやすくなります。誰かがこれらのさまざまなオプションで速度テストを実行する時間があれば、私は結果を見てみたいと思います。

0
Jake Levi

あなた自身の答え(受け入れられたもの)は非常に良く、特に単純です。ただし、1つの重大な非効率性があります。外側のループ_for a in lst_が必要以上に1回実行されます。そのループの最初のパスでは、ijの値は常に同じであるため、乗算と除算は何もしません。あなたの例multinomial([123, 134, 145])には、_123_の不要な乗算と除算があり、コードに時間を追加しています。

パラメータで最大値を見つけて削除することをお勧めします。そうすれば、これらの不要な操作は実行されません。これにより、コードが複雑になりますが、特に多数の短いリストの場合、実行時間が短縮されます。以下の私のコードはmultcoeff(123, 134, 145)を_111_マイクロ秒で実行しますが、コードは_141_マイクロ秒かかります。これは大きな増加ではありませんが、問題になる可能性があります。これが私のコードです。これは、リストではなくパラメータとして個々の値も受け取るため、コードとのもう1つの違いです。

_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
_
0
Rory Daulton