什么是itertools的更快替代品?

时间:2020-02-05 07:56:37

标签: python numpy combinations itertools


k = 7
n = 30

def f(k,n):
    p = []
    for i in range(n):
        p.append(len(list(itertools.combinations([x for x in range(2**k)],i)))

对于较大的变量值,上面的代码工作缓慢并且会因错误而中断的问题。我尝试过sklearn.cartesian,但是当需要组合时得到了permutationa。我知道有一种方法可以使它与numpy一起更快地工作,但是我还没有弄清楚如何实现它。 Similar question有关于numpy的答案,但我不知道np.column_stack((np.repeat(a, b.size),np.tile(b, a.size)))应该如何工作。正如我现在所看到的,我将是数组的众多成员,并且会发生变化,而我并不完全了解如何处理这个事实。

3 个答案:

答案 0 :(得分:2)

使用number of combinations的公式,您可以像下面这样简单地迭代进行此计算:

def f(k, n):
    p = [1]
    f = 1 << k
    for i in range(1, n):
        p.append((p[-1] * f) // i)
        f -= 1
    return p

# For comparison
def f_orig(k, n):
    import itertools
    p = []
    for i in range(n):
        p.append(len(list(itertools.combinations([x for x in range(2 ** k)],i))))
    return p

# Test
k = 4
n = 5
print(f(k, n))
# [1, 16, 120, 560, 1820]
print(f_orig(k, n))
# [1, 16, 120, 560, 1820]

一个小基准:

k = 5
n = 8
%timeit f(k, n)
# 1.55 µs ± 498 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit f_orig(k, n)
# 528 ms ± 1.15 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

但是,随着数量的增加,差异变得更大,而且这不需要任何额外的内存。

答案 1 :(得分:2)

@jdehesa提供了最快的解决方案,它使用乘法公式来(递归地)计算binomial coefficients。以下是其他几次尝试:

from itertools import accumulate
from scipy.special import binom, comb
import math

def f_math_comb(k, n):
    # works with python 3.8
    N = 1 << k  # N = 2**k
    return [math.comb(N, i) for i in range(n)]

def f_scipy_comb(k, n):
    N = 1 << k 
    return [comb(N, i, exact=True) for i in range(n)]

def f_scipy_binom(k, n):
    N = 1 << k 
    return list(map(int, binom(N, range(n))))

def f_itertools_accumulate(k, n):
    N = 1 << k
    p = (N + 1) / np.arange(1, n) - 1
    int_round = lambda x: int(round(x))
    return [1] + list(map(int_round, accumulate(p, mul)))

def f_multip(k, n):
    # jdehesa's solution
    p = [1]
    f = 1 << k
    for i in range(1, n):
        p.append((p[-1] * f) // i)
        f -= 1
    return p

基准:

k = 8
n = 2**k + 1

%timeit f_math_comb(k, n)
3.32 ms ± 45 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit f_scipy_comb(k, n)
3.23 ms ± 75.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit f_scipy_binom(k, n)
189 µs ± 2.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit f_itertools_accumulate(k, n)
1.03 ms ± 12.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit f_multip(k, n)
102 µs ± 10.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

一种可能的改进是使用对称关系:

enter image description here

编辑:不幸的是,scipy的binom并不总是返回准确的结果,因为它使用一些近似值来计算N的大值的二项式系数。类似地,f_itertools_accumulate对于大的N值四舍五入问题,没有给出确切的结果。

答案 2 :(得分:0)

我猜测fk足够大时,您的n遇到内存错误。这种变化应该获得长度而不会耗尽(太多)内存

In [167]: def f1(k,n): 
     ...:     p = [] 
     ...:     for i in range(n): 
     ...:         g = itertools.combinations([x for x in range(2**k)],i) 
     ...:         cnt = 0 
     ...:         for x in g: cnt += 1 
     ...:         p.append(cnt) 
     ...:     return p 

它返回与您的f相同的计数:

In [168]: f1(5,5)                                                                              
Out[168]: [1, 32, 496, 4960, 35960]
In [169]: f(5,5)                                                                               
Out[169]: [1, 32, 496, 4960, 35960]

虽然慢一些。

In [170]: timeit f1(5,5)                                                                       
3.47 ms ± 14 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [171]: timeit f(5,5)                                                                        
2.72 ms ± 122 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [175]: timeit -r1 -n1 f1(5,5)                                                               
3.66 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
In [176]: timeit -r1 -n1 f1(6,5)                                                               
61.4 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
In [177]: timeit -r1 -n1 f1(7,5)                                                               
1.01 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
In [178]: timeit -r1 -n1 f1(8,5)                                                               
14.6 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

尝试为f重复这些时间,我马上得到了killed。我应该从另一端尝试过:

In [179]: timeit -r1 -n1 f(8,5)                                                                
Killed

无论如何,它的确显示了我在不累积的情况下处理的值大于您的方法,即使它的开始速度较慢。

相关问题