如何加速Eratosthenes python列表生成器的筛选

如何加速Eratosthenes python列表生成器的筛选,python,list,computer-science,Python,List,Computer Science,我的问题直接来自CS circles网站。这是页面底部的最后一个问题,叫做“准备起飞”。基本情况是,他们需要一个长度为1000001的列表,如果索引为素数,则每个项的索引为True,如果不是素数,则每个项的索引为False 例如,iPrime[13]是正确的。iPrime[14]是假的 列表的第一个小部分“iPrime”如下所示: isPrime = [False, False, True, True, False, True, False, True, False, False, ...]

我的问题直接来自CS circles网站。这是页面底部的最后一个问题,叫做“准备起飞”。基本情况是,他们需要一个长度为1000001的列表,如果索引为素数,则每个项的索引为True,如果不是素数,则每个项的索引为False

例如,iPrime[13]是正确的。iPrime[14]是假的

列表的第一个小部分“iPrime”如下所示:

isPrime = [False, False, True, True, False, True, False, True, False, False, ...]
我的问题是他们有7秒的时间限制。我在下面有一个工作代码,编号为101,用于调试。当我把它提升到所需的1000001列表长度时,它花费的时间太长了,几分钟后我就终止了这个程序。 这是我的工作(长度101),非常慢的代码:

n = 101
c = 2
isPrime = [False,False]
for i in range(2,n):
    isPrime.append(i)

def ifInt(isPrime):
    for item in isPrime:
        if type(item) == int:
            return item
for d in range(2,n):
    if c != None:
        for i in range(c,n,c):
            isPrime[i] = False
        isPrime[c] = True
        c = ifInt(isPrime)
然后我找到了这个。它的运行时间更快,但只输出一个素数列表,而不是一个长度为n的列表,其中list[n]为素数返回True,否则返回false

我不确定这第二段代码是否真的能在7秒内创建长度为1000001的主列表,但这是我在研究中能找到的最相关的东西

def primes_sieve1(limit):
limitn = limit+1
primes = dict()
for i in range(2, limitn): primes[i] = True

for i in primes:
    factors = range(i,limitn, i)
    for f in factors[1:]:
        primes[f] = False
return [i for i in primes if primes[i]==True]

print primes_sieve1(101)
CS circles似乎非常常用,但我无法找到Python的工作版本。希望这对某人来说是一个简单的解决方案


这个问题与其他问题不同,因为我不仅要快速创建素数列表,还要创建一个从0到n的所有正整数的列表,这些整数由True标记为素数,由False标记为非素数

我看到的第一件事是,生成初始列表(循环和追加)的方式效率低下且不必要。您可以只添加列表,而不是循环和追加每个元素

我看到的第二件事是,您正在进行的类型检查是不必要的,函数调用是昂贵的,您可以重构以完全避免这种情况

最后,我认为在任何筛子实现中都可以得到一件“大事”,那就是利用切片分配。你应该在一次命中中划掉所有因素,而不是循环。例如:

from math import sqrt

def primes(n):
    r = [True] * n
    r[0] = r[1] = False
    r[4::2] = [False] * len(r[4::2])
    for i in xrange(int(1 + sqrt(n))):
        if r[i]:
            r[3*i::2*i] = [False] * len(r[3*i::2*i])
    return r
注意,我还有几个其他技巧:

  • 立即划掉偶数,避免一半的工作量
  • 只需要迭代到长度的sqrt
在我的破旧的动力不足的macbook上,这段代码可以在大约75毫秒内生成1000001个列表:

>>> timeit primes(1000001)
10 loops, best of 3: 75.4 ms per loop

我意识到有很多优化,但其他人很少解释prime sieve算法,因此初学者或算法的第一次创建者很难接近它们。这里的所有解决方案都是用python编写的,在速度和优化方面都在同一页上。这些解决方案将逐渐变得更快、更复杂。:)

香草溶液 这是筛子的一个非常简单的实现。在继续之前,请确保您了解上述情况。唯一需要注意的是,你开始在i+i而不是i处标记非素数,但这是相当明显的。(因为你假设我本身就是素数)。为了使测试公平,所有数字都将出现在多达2500万的名单上

real    0m7.663s  
user    0m7.624s  
sys     0m0.036s  
小改进1(平方根): 我将尝试按照直接到不太直接的变化对它们进行排序。注意,我们不需要迭代到n,而是只需要到n的平方根。原因是,任何n以下的复合数,其素因子必须小于或等于n的平方根。当你用手进行筛选时,你会注意到所有n的平方根上的“未筛选”数字都是默认的素数

另一个注意事项是,当平方根变成整数时,您必须稍微小心一点,因此在这种情况下,您应该添加一个,以便它覆盖它。也就是说,在n=49时,您希望循环到7(包括7),或者您可能会得出49是素数的结论

def primes1(n):
    r = [True] * n
    r[0] = r[1] = False
    for i in xrange(int(n**0.5+1)):
        if r[i]:
            for j in xrange(i+i, n, i):
                r[j] = False
    return r

real    0m4.615s
user    0m4.572s
sys     0m0.040s
请注意,它的速度相当快。当你考虑它的时候,你只循环到平方根,所以现在需要2500万次顶级迭代就只需要5000次顶级迭代

小改进2(在内部循环中跳过): 注意,在内部循环中,我们可以从i*i开始,而不是从i+i开始。这源于一个与平方根非常相似的论点,但大的想法是,i和i*i之间的任何组合都已经被更小的素数标记

def primes2(n):
    r = [True] * n
    r[0] = r[1] = False
    for i in xrange(int(n**0.5+1)):
        if r[i]:
            for j in xrange(i*i, n, i):
                r[j]=False
    return r

real    0m4.559s
user    0m4.500s
sys     0m0.056s
这有点令人失望。但是,嘿,速度更快

有点大的改进3(甚至跳过): 这里的想法是,我们可以预先标记所有偶数索引,然后在主循环中跳过2次迭代。之后,我们可以在3开始外循环,而内循环可以跳过2*i。(因为取而代之的是i意味着它是偶数,(i+i)(i+i+i+i)等等)

酷的改进4(wim的想法): 这个解决方案是一个相当高级的技巧。切片分配比循环快,因此它使用python的切片表示法:
r[begin:end:skip]

def primes4(n):
    r = [True] * n
    r[0] = r[1] = False 
    r[4::2] = [False] * len(r[4::2])
    for i in xrange(3, int(1 + n**0.5), 2):
        if r[i]:
            r[i*i::2*i] = [False] * len(r[i*i::2*i])
    return r

10 loops, best of 3: 1.1 sec per loop
轻微改善5 请注意,python在计算长度时会重新划分
r[4::2]
,因此这需要相当多的额外时间,因为我们只需要计算长度。不过,我们确实使用了一些糟糕的数学来实现这一点

def primes5(n):
    r = [True] * n
    r[0] = r[1] = False 
    r[4::2] = [False] * ((n+1)/2-2)
    for i in xrange(3, int(1 + n**0.5), 2):
        if r[i]:
            r[i*i::2*i] = [False] * ((n+2*i-1-i*i)/(2*i))
    return r

10 loops, best of 3: 767 msec per loop
分配加速(帕德雷克·坎宁安): 请注意,我们指定一个数组为all True,然后将一半(evens)设置为False。我们可以从一个交替的布尔数组开始

def primes6(n):
    r = [False, True] * (n//2) + [True]
    r[1], r[2] = False, True
    for i in xrange(3, int(1 + n**0.5), 2):
        if r[i]:
            r[i*i::2*i] = [False] * ((n+2*i-1-i*i)/(2*i))
    return r

10 loops, best of 3: 717 msec per loop
请不要引用我的话,但我认为如果没有一些糟糕的数学方法,最后一个版本就没有明显的改进。我尝试过的一个可爱的特性,但没有更快,就是注意到除2,3之外的素数必须是6k+1或6k-1的形式。(请注意,如果它是6k,那么可以被6整除,6k+2 | 2,6k+3 | 3,6k+4 | 2,6k+5与-1 mod 6是一致的。这表明我们可以每次跳过6并检查两边。要么是我的实现不好,要么是python
def primes5(n):
    r = [True] * n
    r[0] = r[1] = False 
    r[4::2] = [False] * ((n+1)/2-2)
    for i in xrange(3, int(1 + n**0.5), 2):
        if r[i]:
            r[i*i::2*i] = [False] * ((n+2*i-1-i*i)/(2*i))
    return r

10 loops, best of 3: 767 msec per loop
def primes6(n):
    r = [False, True] * (n//2) + [True]
    r[1], r[2] = False, True
    for i in xrange(3, int(1 + n**0.5), 2):
        if r[i]:
            r[i*i::2*i] = [False] * ((n+2*i-1-i*i)/(2*i))
    return r

10 loops, best of 3: 717 msec per loop
def primes_wim_opt(n):
    r = [False, True] * (n // 2)
    r[0] = r[1] = False
    r[2] = True
    for i in xrange(int(1 + n ** .5)):
        if r[i]:
            r[3*i::2*i] = [False] * len(r[3*i::2*i])
    return r
In [9]: timeit primesVanilla(100000)
10 loops, best of 3: 25.7 ms per loop

In [10]: timeit primes_wim(100000)
100 loops, best of 3: 3.59 ms per loop

In [11]: timeit primes1(100000)
100 loops, best of 3: 14.8 ms per loop

In [12]: timeit primes_wim_opt(100000)
100 loops, best of 3: 2.18 ms per loop

In [13]: timeit primes2(100000)
100 loops, best of 3: 14.7 ms per loop

In [14]: primes_wim(100000) ==  primes_wim_opt(100000) ==  primes(100000) == primesVanilla(100000) == primes2(100000)
Out[14]: True
In [76]: timeit primesVanilla(100000)
10 loops, best of 3: 22.3 ms per loop

In [77]: timeit primes_wim(100000)
100 loops, best of 3: 2.92 ms per loop

In [78]: timeit primes1(100000)
100 loops, best of 3: 10.9 ms per loop

In [79]: timeit primes_wim_opt(100000)
1000 loops, best of 3: 1.88 ms per loop

In [80]: timeit primes2(100000)
100 loops, best of 3: 10.3 ms per loop
In [81]: primes_wim(100000) ==  primes_wim_opt(100000) ==  primes(100000) == primesVanilla(100000) == primes2(100000)
Out[80]: True
def primes_wim_opt(n):
    is_odd = n % 2 & 1    
    r = [False, True] * (n // 2 + is_odd)
    r[0] = r[1] = False
    r[2] = True
    for i in range(int(1 + n ** .5)):
        if r[i]:
            r[3*i::2*i] = [False] * len(range(3*i,len(r), 2 * i))
    return r
In [16]: timeit primes_wim_opt_2(100000)
1000 loops, best of 3: 1.38 ms per loop
In [10]: timeit  primes_wim_opt_2(100000)
1000 loops, best of 3: 1.60 ms per loop
def primes_wim_opt_2(n):
    is_odd = n % 2 & 1
    r = [False, True] * ((n // 2) + is_odd)
    r[0] = r[1] = False
    r[2] = True
    for i in range(int(1 + n ** .5)):
        if r[i]:
            r[3*i::2*i] = [False] * (((n - 3 * i) // (2 * i)) + 1)
    return r
In [12]: timeit primes_wim_opt_2(100000)
1000 loops, best of 3: 1.32 ms per loop

In [6]: timeit primes5(100000)
100 loops, best of 3: 2.47 ms per loop
def primes_wim_opt_2(n):
    r = [False, True] * (n // 2)
    r[0] = r[1] = False
    r[2] = True
    for i in range(3, int(1 + n ** .5),2):
        if r[i]:
            r[3*i::2*i] = [False] * (((n - 3 * i) // (2 * i)) + 1)
    return r
In [2]: timeit primes_wim_opt_2(100000)
1000 loops, best of 3: 1.10 ms per loop
In [2]: timeit primes_wim_opt_2(100000)
1000 loops, best of 3: 1.29 ms per loop