计算有和没有SciPy的k组合的数量

时间:2014-12-17 17:27:31

标签: python scipy combinations benchmarking

我感到困惑的是,函数comb of SciPy似乎比天真的Python实现慢。这是解决Problem 53 of Project Euler的两个等效程序的测量时间。


使用SciPy:

%%timeit
from scipy.misc import comb

result = 0
for n in range(1, 101):
    for k in range(1, n + 1):
        if comb(n, k) > 1000000:
            result += 1
result

输出:

1 loops, best of 3: 483 ms per loop

没有SciPy:

%%timeit
from math import factorial

def comb(n, k):
    return factorial(n) / factorial(k) / factorial(n - k)

result = 0
for n in range(1, 101):
    for k in range(1, n + 1):
        if comb(n, k) > 1000000:
            result += 1
result

输出:

10 loops, best of 3: 86.9 ms per loop

第二个版本大约快5倍(在两台Mac上测试,python-2.7.9-1,IPython 2.3.1-py27_0)。这是SciPy comb函数的预期行为(source code)吗?我做错了什么?


编辑(来自Anaconda 3.7.3-py27_0发行版的SciPy):

import scipy; print scipy.version.version
0.12.0

编辑(IPython以外的相同区别):

$ time python with_scipy.py
real    0m0.700s
user    0m0.610s
sys     0m0.069s

$ time python without_scipy.py
real    0m0.134s
user    0m0.099s
sys     0m0.015s

3 个答案:

答案 0 :(得分:7)

看起来您可能正在错误地运行计时并测量将scipy加载到内存中所需的时间。我跑的时候:

import timeit
from scipy.misc import comb
from math import factorial

def comb2(n, k):
    return factorial(n) / factorial(k) / factorial(n - k)

def test():
    result = 0
    for n in range(1, 101):
        for k in range(1, n + 1):
            if comb(n, k) > 1000000:
                result += 1

def test2():
    result = 0
    for n in range(1, 101):
        for k in range(1, n + 1):
            if comb2(n, k) > 1000000:
                result += 1

T = timeit.Timer(test)
print T.repeat(3,50)

T2 = timeit.Timer(test2)
print T2.repeat(3,50)

我明白了:

[2.2370951175689697, 2.2209839820861816, 2.2142510414123535]
[2.136591911315918, 2.138144016265869, 2.1437559127807617]

表示scipy比python版本更快

答案 1 :(得分:3)

回答我自己的问题。 It seems在SciPy中存在两个不同的功能。我不太清楚为什么。但是另一个binomcomb快8.5倍,比我快1.5倍,这让人放心:

%%timeit
from scipy.special import binom
result = 0
for n in range(1, 101):
    for k in range(1, n + 1):
        if binom(n, k) > 1000000:
            result += 1
result

输出:

10 loops, best of 3: 56.3 ms per loop

SciPy 0.14.0伙伴们,这对你有用吗?

答案 2 :(得分:1)

我相信这比其他提到的纯python方法更快:

from math import factorial
from operator import mul
def n_choose_k(n, k):
    if k < n-k:
        return reduce(mul, xrange(n-k+1, n+1)) // factorial(k)
    else:
        return reduce(mul, xrange(k+1, n+1)) // factorial(n-k)

与numpy解决方案相比,它的速度很慢,但请注意NumPy并没有在&#34;无限整数中工作。像Python一样。这意味着,虽然速度慢,但Python会设法返回正确的结果。 NumPy的默认行为对于组合数据并不总是理想的(至少在涉及非常大的整数时不是这样)。

E.g。

In [1]: from scipy.misc import comb

In [2]: from scipy.special import binom

In [3]: %paste
    from math import factorial
    from operator import mul
    def n_choose_k(n, k):
        if k < n-k:
            return reduce(mul, xrange(n-k+1, n+1)) // factorial(k)
        else:
            return reduce(mul, xrange(k+1, n+1)) // factorial(n-k)

## -- End pasted text --

In [4]: n_choose_k(10, 3), binom(10, 3), comb(10, 3)
Out[4]: (120, 120.0, 120.0)

In [5]: n_choose_k(1000, 250) == factorial(1000)//factorial(250)//factorial(750)
Out[5]: True

In [6]: abs(comb(1000, 250) - n_choose_k(1000, 250))
Out[6]: 3.885085558125553e+230

In [7]: abs(binom(1000, 250) - n_choose_k(1000, 250))
Out[7]: 3.885085558125553e+230

错误是如此之大并不奇怪,它只是截断的影响。截断为什么? NumPy将自己限制为使用64位来表示整数。然而,对这么大的整数的要求是相当多的:

In [8]: from math import log

In [9]: log((n_choose_k(1000, 250)), 2)  
Out[9]: 806.1764820287578

In [10]: type(binom(1000, 250))
Out[10]: numpy.float64