Answers:
请参阅scipy.special.comb(旧版本的scipy中的scipy.misc.comb)。当exact
为False时,它使用伽马函数来获得良好的精度而无需花费很多时间。在确切的情况下,它返回一个任意精度的整数,这可能需要很长时间才能计算出来。
为什么不自己写呢?这是一线之类的:
from operator import mul # or mul=lambda x,y:x*y
from fractions import Fraction
def nCk(n,k):
return int( reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1) )
测试-打印Pascal的三角形:
>>> for n in range(17):
... print ' '.join('%5d'%nCk(n,k) for k in range(n+1)).center(100)
...
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
1 10 45 120 210 252 210 120 45 10 1
1 11 55 165 330 462 462 330 165 55 11 1
1 12 66 220 495 792 924 792 495 220 66 12 1
1 13 78 286 715 1287 1716 1716 1287 715 286 78 13 1
1 14 91 364 1001 2002 3003 3432 3003 2002 1001 364 91 14 1
1 15 105 455 1365 3003 5005 6435 6435 5005 3003 1365 455 105 15 1
1 16 120 560 1820 4368 8008 11440 12870 11440 8008 4368 1820 560 120 16 1
>>>
PS。编辑以替换int(round(reduce(mul, (float(n-i)/(i+1) for i in range(k)), 1)))
,int(reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1))
因此对于大N / K不会出错
from functools import reduce
。
在Google代码上快速搜索给出了(它使用了@Mark Byers的答案中的公式):
def choose(n, k):
"""
A fast way to calculate binomial coefficients by Andrew Dalke (contrib).
"""
if 0 <= k <= n:
ntok = 1
ktok = 1
for t in xrange(1, min(k, n - k) + 1):
ntok *= n
ktok *= t
n -= 1
return ntok // ktok
else:
return 0
choose()
比scipy.misc.comb()
您需要确切答案快10倍(在所有0 <=(n,k)<1e3对上测试)。
def comb(N,k): # from scipy.comb(), but MODIFIED!
if (k > N) or (N < 0) or (k < 0):
return 0L
N,k = map(long,(N,k))
top = N
val = 1L
while (top > (N-k)):
val *= top
top -= 1
n = 1L
while (n < k+1L):
val /= n
n += 1
return val
choose
功能应该有更多的投票权!Python 3.8具有math.comb,但是我不得不使用Python 3.6进行挑战,并且没有实现能够为非常大的整数提供准确的结果。这个做的很快。
如果您想要确切的结果和速度,请尝试gmpy-gmpy.comb
应该完全按照您的要求进行操作,而且速度非常快(当然,作为gmpy
的原始作者,我有偏见;-)。
gmpy2.comb()
超过10倍的速度choose()
从我的答案代码:for k, n in itertools.combinations(range(1000), 2): f(n,k)
这里f()
是要么gmpy2.comb()
或choose()
Python的3
如果您想要精确的结果,请使用sympy.binomial
。看来这是最快的方法。
x = 1000000
y = 234050
%timeit scipy.misc.comb(x, y, exact=True)
1 loops, best of 3: 1min 27s per loop
%timeit gmpy.comb(x, y)
1 loops, best of 3: 1.97 s per loop
%timeit int(sympy.binomial(x, y))
100000 loops, best of 3: 5.06 µs per loop
从开始Python 3.8
,标准库现在包括math.comb
用于计算二项式系数的函数:
math.comb(n,k)
这是从n个项中不重复选择k个项的方法的数量n! / (k! (n - k)!)
:
import math
math.comb(10, 5) # 252
这是另一种选择。该代码最初是用C ++编写的,因此可以将其反向移植到C ++以获取有限精度的整数(例如__int64)。优点是(1)它仅涉及整数运算,(2)通过执行连续的乘法和除法对,避免了膨胀整数值。我已经用Nas Banov的Pascal三角形测试了结果,它得到了正确的答案:
def choose(n,r):
"""Computes n! / (r! (n-r)!) exactly. Returns a python long int."""
assert n >= 0
assert 0 <= r <= n
c = 1L
denom = 1
for (num,denom) in zip(xrange(n,n-r,-1), xrange(1,r+1,1)):
c = (c * num) // denom
return c
基本原理:为了最小化乘法和除法的数量,我们将表达式重写为
n! n(n-1)...(n-r+1)
--------- = ----------------
r!(n-r)! r!
为了尽可能避免乘法溢出,我们将按照以下STRICT顺序从左到右进行评估:
n / 1 * (n-1) / 2 * (n-2) / 3 * ... * (n-r+1) / r
我们可以证明按此顺序运算的整数算术是精确的(即无舍入误差)。
使用动态编程,时间复杂度为Θ(n * m),空间复杂度为Θ(m):
def binomial(n, k):
""" (int, int) -> int
| c(n-1, k-1) + c(n-1, k), if 0 < k < n
c(n,k) = | 1 , if n = k
| 1 , if k = 0
Precondition: n > k
>>> binomial(9, 2)
36
"""
c = [0] * (n + 1)
c[0] = 1
for i in range(1, n + 1):
c[i] = 1
j = i - 1
while j > 0:
c[j] += c[j - 1]
j -= 1
return c[k]
您可以编写2个简单的函数,实际上比使用scipy.special.comb快5到8倍。实际上,您不需要导入任何额外的程序包,并且该函数非常易于阅读。诀窍是使用备忘录存储先前计算的值,并使用nCr的定义
# create a memoization dictionary
memo = {}
def factorial(n):
"""
Calculate the factorial of an input using memoization
:param n: int
:rtype value: int
"""
if n in [1,0]:
return 1
if n in memo:
return memo[n]
value = n*factorial(n-1)
memo[n] = value
return value
def ncr(n, k):
"""
Choose k elements from a set of n elements - n must be larger than or equal to k
:param n: int
:param k: int
:rtype: int
"""
return factorial(n)/(factorial(k)*factorial(n-k))
如果我们比较时间
from scipy.special import comb
%timeit comb(100,48)
>>> 100000 loops, best of 3: 6.78 µs per loop
%timeit ncr(100,48)
>>> 1000000 loops, best of 3: 1.39 µs per loop
当n大于20时,直接公式会产生大整数。
因此,另一个回应是:
from math import factorial
reduce(long.__mul__, range(n-r+1, n+1), 1L) // factorial(r)
简短,准确和高效,因为它通过坚持使用long避免了python大整数。
与scipy.special.comb相比,它更准确,更快捷:
>>> from scipy.special import comb
>>> nCr = lambda n,r: reduce(long.__mul__, range(n-r+1, n+1), 1L) // factorial(r)
>>> comb(128,20)
1.1965669823265365e+23
>>> nCr(128,20)
119656698232656998274400L # accurate, no loss
>>> from timeit import timeit
>>> timeit(lambda: comb(n,r))
8.231969118118286
>>> timeit(lambda: nCr(128, 20))
3.885951042175293
range(n-r+1, n+1)
改为range(n-r,n+1)
。
这是使用内置备忘录修饰器的@ killerT2333代码。
from functools import lru_cache
@lru_cache()
def factorial(n):
"""
Calculate the factorial of an input using memoization
:param n: int
:rtype value: int
"""
return 1 if n in (1, 0) else n * factorial(n-1)
@lru_cache()
def ncr(n, k):
"""
Choose k elements from a set of n elements,
n must be greater than or equal to k.
:param n: int
:param k: int
:rtype: int
"""
return factorial(n) / (factorial(k) * factorial(n - k))
print(ncr(6, 3))
对于相当大的输入,这可能与在纯python中完成的速度一样快:
def choose(n, k):
if k == n: return 1
if k > n: return 0
d, q = max(k, n-k), min(k, n-k)
num = 1
for n in xrange(d+1, n+1): num *= n
denom = 1
for d in xrange(1, q+1): denom *= d
return num / denom
scipy.misc.comb
scipy.special.comb
从版本开始不推荐使用0.10.0
。