Eratosthenes筛子-查找素数Python


74

只是为了澄清,这不是一个作业问题:)

我想为正在构建的数学应用程序找到质数,并遇到了Eratosthenes方法的Sieve

我已经用Python编写了一个实现。但这太慢了。可以说,如果我想找到所有小于200万的素数。耗时> 20分钟。(我此时已停止)。我怎样才能加快速度?

def primes_sieve(limit):
    limitn = limit+1
    primes = range(2, limitn)

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

print primes_sieve(2000)

更新: 我最终对这段代码进行了分析,发现花了很多时间从列表中删除一个元素。考虑到它必须遍历整个列表(最坏的情况)以找到元素,然后删除它,然后重新调整列表(也许会继续复制吗?),这是完全可以理解的。无论如何,我掏出了字典的清单。我的新实现-

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(2000000)

1
在这里stackoverflow.com/questions/2897297还有一个类似的问题,您可能会觉得有用。
Scott Griffiths 2010年

检查答案。
tzot 2010年


@Srikar:您可以迭代到极限的平方根,而不是迭代到极限,因为字典中的任何复合数字都将比极限的平方根小一个因数。
sayantankhan

1
使用该step参数range很出色。factors是误称,应该是multiples
汤姆·罗素

Answers:


112

您没有完全实现正确的算法:

在您的第一个示例中,primes_sieve不维护要触发/未设置的素数标志的列表(如算法中一样),而是连续调整整数列表的大小,这非常昂贵:从列表中删除一个项需要移动所有后续项下降一个

在第二个示例中,primes_sieve1维护素数标志的字典,这是朝着正确方向迈出的一步,但是它以未定义的顺序遍历字典,并多余地剔除因数的因子(而不是像算法中那样仅素数的因子) )。您可以通过对键进行排序并跳过非质数(这已经使其速度提高了一个数量级)来解决此问题,但是直接使用列表仍然更加有效。

正确的算法(使用列表而不是字典)看起来像:

def primes_sieve2(limit):
    a = [True] * limit                          # Initialize the primality list
    a[0] = a[1] = False

    for (i, isprime) in enumerate(a):
        if isprime:
            yield i
            for n in range(i*i, limit, i):     # Mark factors non-prime
                a[n] = False

(请注意,这还包括从素数的平方(i*i)而不是其双数开始的非素数标记的算法优化。)


8
可以进行另一种优化,您的步长xrange(i*i,limit,i)可以调整2*i
st0le 2010年

3
我喜欢您对Eratosthenes筛网的简洁实现。:)但是,我遇到了OverflowError:Python int太大,无法转换为C long。我将xrange(i * i,limit,i)更改为xrange(i,limit,i)。感谢您分享此代码段!
Annie Lagang 2012年

11
@ st0le:否,无法设置步长2*i。刚刚尝试过。它产生14作为质数。
mpen 2012年

2
@Mark,对不起,我没有真正完整地解释它。通过i=2使用步长进行迭代来消除所有偶数,i但其余的都可以使用2*i。实际上,在我的实现中,我使用了一半的布尔值,因为我不存储偶数,而是使用simple mod 2。您可以在这里找到我的Java实现,它使用的内存更少(1/8)。在这里
st0le 2012年

4
+1,只是一个小细节,如果用于[False] * 2 + [True] * (limit-2)初始化,则可以避免在将数字<2作为参数传递时使用IndexError
Jan Vorcak 2013年


7

从数组(列表)的开头删除需要将其后的所有项目下移。这意味着从开头开始以这种方式从列表中删除每个元素都是O(n ^ 2)操作。

您可以使用集合更有效地执行此操作:

def primes_sieve(limit):
    limitn = limit+1
    not_prime = set()
    primes = []

    for i in range(2, limitn):
        if i in not_prime:
            continue

        for f in range(i*2, limitn, i):
            not_prime.add(f)

        primes.append(i)

    return primes

print primes_sieve(1000000)

...或者,避免重新排列列表:

def primes_sieve(limit):
    limitn = limit+1
    not_prime = [False] * limitn
    primes = []

    for i in range(2, limitn):
        if not_prime[i]:
            continue
        for f in xrange(i*2, limitn, i):
            not_prime[f] = True

        primes.append(i)

    return primes

2
有关优化,请参见下面的@Piet Delport答案:将i*2上面替换为i*i
总统詹姆斯·波尔克(James K. Polk)2010年

4

快多了:

import time
def get_primes(n):
  m = n+1
  #numbers = [True for i in range(m)]
  numbers = [True] * m #EDIT: faster
  for i in range(2, int(n**0.5 + 1)):
    if numbers[i]:
      for j in range(i*i, m, i):
        numbers[j] = False
  primes = []
  for i in range(2, m):
    if numbers[i]:
      primes.append(i)
  return primes

start = time.time()
primes = get_primes(10000)
print(time.time() - start)
print(get_primes(100))

2

我意识到这并没有真正回答如何快速生成素数的问题,但是也许有些人会发现这种替代方法很有趣:由于python通过生成器提供了惰性评估,因此eratosthenes的筛子可以完全按照以下说明来实现:

def intsfrom(n):
    while True:
        yield n
        n += 1

def sieve(ilist):
    p = next(ilist)
    yield p
    for q in sieve(n for n in ilist if n%p != 0):
        yield q


try:
    for p in sieve(intsfrom(2)):
        print p,

    print ''
except RuntimeError as e:
    print e

这里有try块,因为算法一直运行到炸毁堆栈为止,并且没有try块,则显示回溯,从而将要查看的实际输出推到了屏幕之外。


4
不,这不是Eratosthenes的筛子,而是审判分庭的筛子。即使这样也不是很理想,因为它不会被推迟:任何候选数仅需通过不超过其平方根的素数进行测试。沿着上面链接的答案(后面的答案)底部的伪代码行实现此操作,将使您的代码获得极大的加速(甚至在切换到适当的筛子之前),并且//因为这将极大地减少堆栈的使用-因此try毕竟您可能不需要屏蔽。
Will Ness 2014年

...另请参阅:有关“ sqrt”问题及其影响的更多讨论用于推迟的试验部门实际Python代码以及一些相关的Scala。---如果您自己想出了该代码,对您也表示敬意!:)
内斯

有趣的是,尽管我还不明白为什么我所讲的东西不同于Eratosthenes的筛子。我认为这被描述为将2中的所有整数放在一条直线上,然后反复地将该直线中的第一作为素数并删除所有倍数。“如果n%p!= 0,则ilist中的n为n”代表删除了倍数。绝对是高度次优的,当然
Paul Gardiner

1
n for n in ilist if n%p != 0测试n范围内每个数字的可除性p; 但range(p*p, N, p)可以直接生成倍数,而无需测试所有这些数字。
Will Ness 2015年

2

通过结合许多爱好者(包括上面评论中的Glenn Maynard和MrHIDEn)的贡献,我想到了以下python 2代码:

def simpleSieve(sieveSize):
    #creating Sieve.
    sieve = [True] * (sieveSize+1)
    # 0 and 1 are not considered prime.
    sieve[0] = False
    sieve[1] = False
    for i in xrange(2,int(math.sqrt(sieveSize))+1):
        if sieve[i] == False:
            continue
        for pointer in xrange(i**2, sieveSize+1, i):
            sieve[pointer] = False
    # Sieve is left with prime numbers == True
    primes = []
    for i in xrange(sieveSize+1):
        if sieve[i] == True:
            primes.append(i)
    return primes

sieveSize = input()
primes = simpleSieve(sieveSize)

在我的机器上以10为幂的不同输入进行计算所需的时间为:

  • 3:0.3毫秒
  • 4:2.4毫秒
  • 5:23毫秒
  • 6:0.26 s
  • 7:3.1秒
  • 8:33秒

不再需要与True或False进行比较,因为它们已经是布尔值了,
Copperfield

@Copperfield谢谢!它帮助将速度提高了10-20%。
Ajay

sieve = [True] * (sieveSize+1)是比我的解决方案快,但sieve[0]/[1]xrange(sieveSize+1)在素数[]没有改善任何事情。xrange(2, sieveSize+1)是好名声。:)。同样,for i in xrange(2,int(math.sqrt(sieveSize))+1):我们也可以只使用for i in xrange(2, int((sieveSize+1)**0.5):Good代码。:)
MrHIDEn

1

一个简单的速度技巧:定义变量“素数”时,将步骤设置为2以自动跳过所有偶数,并将起点设置为1。

然后,您可以进一步优化,而不是用质数i代替,而用质数i代替[:round(len(primes)** 0.5)]。这将大大提高性能。此外,您可以消除以5结尾的数字,以进一步提高速度。


1

我的实现:

import math
n = 100
marked = {}
for i in range(2, int(math.sqrt(n))):
    if not marked.get(i):
        for x in range(i * i, n, i):
            marked[x] = True

for i in range(2, n):
    if not marked.get(i):
        print i

我只是测试了您的代码,发现dict解决方案比list解决方案慢2倍。
MrHIDEn '16

1

这是一个内存效率更高的版本(并且:适当的筛子,而不是试验部门)。基本上,不是保留所有数字的数组,而是剔除不是质数的数组,而是保留一组计数器(针对发现的每个质数一个计数器),并在推定的质数之前跳过它们。这样,它使用与素数成正比的存储,而不是与最高素数成正比。

import itertools

def primes():

    class counter:
        def __init__ (this,  n): this.n, this.current,  this.isVirgin = n, n*n,  True
            # isVirgin means it's never been incremented
        def advancePast (this,  n): # return true if the counter advanced
            if this.current > n:
                if this.isVirgin: raise StopIteration # if this is virgin, then so will be all the subsequent counters.  Don't need to iterate further.
                return False
            this.current += this.n # pre: this.current == n; post: this.current > n.
            this.isVirgin = False # when it's gone, it's gone
            return True

    yield 1
    multiples = []
    for n in itertools.count(2):
        isPrime = True
        for p in (m.advancePast(n) for m in multiples):
            if p: isPrime = False
        if isPrime:
            yield n
            multiples.append (counter (n))

您会注意到这primes()是一个生成器,因此您可以将结果保存在列表中,也可以直接使用它们。这是第一个n素数:

import itertools

for k in itertools.islice (primes(),  n):
    print (k)

为了完整起见,这里有一个计时器来衡量性能:

import time

def timer ():
    t,  k = time.process_time(),  10
    for p in primes():
        if p>k:
            print (time.process_time()-t,  " to ",  p,  "\n")
            k *= 10
            if k>100000: return

万一您想知道,我还写primes()了一个简单的迭代器(使用__iter____next__),它的运行速度几乎相同。我也很惊讶


一个有趣的想法-如果您将素数计数器存储在最小堆中(内部循环将是O(log num_primes)而不是O(num_primes),它将提高性能)
anthonybell

为什么?即使它们堆在一起,我们仍然必须考虑到每一个。
Jules

如果将每个素数存储在以其下一个值作为键的堆中,则只需查看其下一个值为当前值n的素数。最大的素数将下沉到堆的底部,因此需要比较小的素数少得多地进行评估。
anthonybell '17

1

由于速度,我更喜欢NumPy。

import numpy as np

# Find all prime numbers using Sieve of Eratosthenes
def get_primes1(n):
    m = int(np.sqrt(n))
    is_prime = np.ones(n, dtype=bool)
    is_prime[:2] = False  # 0 and 1 are not primes

    for i in range(2, m):
        if is_prime[i] == False:
            continue
        is_prime[i*i::i] = False

    return np.nonzero(is_prime)[0]

# Find all prime numbers using brute-force.
def isprime(n):
    ''' Check if integer n is a prime '''
    n = abs(int(n))  # n is a positive integer
    if n < 2:  # 0 and 1 are not primes
        return False
    if n == 2:  # 2 is the only even prime number
        return True
    if not n & 1:  # all other even numbers are not primes
        return False
    # Range starts with 3 and only needs to go up the square root
    # of n for all odd numbers
    for x in range(3, int(n**0.5)+1, 2):
        if n % x == 0:
            return False
    return True

# To apply a function to a numpy array, one have to vectorize the function
def get_primes2(n):
    vectorized_isprime = np.vectorize(isprime)
    a = np.arange(n)
    return a[vectorized_isprime(a)]

检查输出:

n = 100
print(get_primes1(n))
print(get_primes2(n))    
    [ 2  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97]
    [ 2  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97]

比较Juraster Notebook上Eratosthenes筛的速度和蛮力。Eratosthenes筛子的速度比暴力破解百万元素快539倍。

%timeit get_primes1(1000000)
%timeit get_primes2(1000000)
4.79 ms ± 90.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.58 s ± 31.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

您的内循环内容是否应该更好地考虑一遍(将先前的答案和评论考虑在内)if is_prime[i]: is_prime[i*i::2*i]=False
Lutz Lehmann

1

我认为必须可以简单地将空列表用作循环的终止条件,并提出了以下建议:

limit = 100
ints = list(range(2, limit))   # Will end up empty

while len(ints) > 0:
    prime = ints[0]
    print prime
    ints.remove(prime)
    i = 2
    multiple = prime * i
    while multiple <= limit:
        if multiple in ints:
            ints.remove(multiple)
        i += 1
        multiple = prime * i

1
import math
def sieve(n):
    primes = [True]*n
    primes[0] = False
    primes[1] = False
    for i in range(2,int(math.sqrt(n))+1):
            j = i*i
            while j < n:
                    primes[j] = False
                    j = j+i
    return [x for x in range(n) if primes[x] == True]

1

使用一些 numpy,我可以在2秒多的时间内找到所有低于1亿的素数。

有两个关键功能,应注意

  • 切出i仅用于的倍数i达根n
  • 的倍数设定iFalse使用x[2*i::i] = False比for循环的明确蟒蛇快得多。

这两个可以大大加快您的代码的速度。对于低于一百万的限制,没有可察觉的运行时间。

import numpy as np

def primes(n):
    x = np.ones((n+1,), dtype=np.bool)
    x[0] = False
    x[1] = False
    for i in range(2, int(n**0.5)+1):
        if x[i]:
            x[2*i::i] = False

    primes = np.where(x == True)[0]
    return primes

print(len(primes(100_000_000)))

1

我能想到的最快的实现:

isprime = [True]*N
isprime[0] = isprime[1] = False
for i in range(4, N, 2):
    isprime[i] = False
for i in range(3, N, 2):
    if isprime[i]:
        for j in range(i*i, N, 2*i):
            isprime[j] = False

1

我只是想出了这个。它可能不是最快的,但是除了直接添加和比较之外,我没有使用其他任何东西。当然,您在这里停止的是递归限制。

def nondivsby2():
    j = 1
    while True:
        j += 2
        yield j

def nondivsbyk(k, nondivs):
    j = 0
    for i in nondivs:
        while j < i:
            j += k
        if j > i:
            yield i

def primes():
    nd = nondivsby2()
    while True:
        p = next(nd)
        nd = nondivsbyk(p, nd)
        yield p

def main():
    for p in primes():
        print(p)

非常好的配方,干净,清晰,简洁!我将其添加为书签。当然,要产生第100个素数,nd链条的深度将达到99级。但实际上只需要10个。而且,随着我们在素数列表上走得越来越远,情况变得越来越糟。您能找到解决这个问题的方法吗?:)
内斯

另外,我在这里确实看不到任何递归,因此在这里也不应该有任何限制。(当然,我几乎完全不了解Python)
Ness将于

起初我感到RecursionError: maximum recursion depth exceeded异常惊讶,这是我得到的例外。但是后来我认为这是有道理的。因为我们可以将生成器视为具有__next__函数的对象。因此,每个nondivsbyk生成器都是同一类的对象(只有不同​​的初始化)。假设我们调用了class_nondivsbyk,那么当一个调用另一个时,它实际上在另一个对象上class_nondivsbyk.__next__调用class_nondivsbyk.__next__了另一个。
塔米尔

大约第100个素数只需要前10个素数,因此首先我可以说(只要我不想给出上限),我们需要在途中“收集”素数,因此创建这些生成器似乎是必要的。我现在真的不知道是否可以“跳过”那些无关的计算。因为现在,我让每一个都检查它是否是一个分隔符,但是如果我把它们放在一边,当数字增加并且我不知道如何将其集成到递归中时,我将需要其他东西来“触发”它们。我还制作了一个“扁平”版本,我可以在那里看看。感谢@WillNess
塔米尔

nd赋值后的两个snd = nondivsbyk(p, nd)应该是两个不同的对象。即,首先nd是引用对象的变量;然后通过函数调用构造新的生成器对象,并将其分配给相同的变量(该变量会忘记其旧值)。但在内部,新的生成器对象是指较旧的-不同-对象。但是正如我所说,我不了解Python。关于10个质数与100个质数的关系-这是一个提示:希望每次调用primes()都会创建一个单独的新生成器对象。(或者什么是适当的术语?)
Ness将于

0

我认为这是用eratosthenes方法查找素数的最短代码

def prime(r):
    n = range(2,r)
    while len(n)>0:
        yield n[0]
        n = [x for x in n if x not in range(n[0],r,n[0])]


print(list(prime(r)))

1
但是,这种表现绝对是可怕的。它在每次迭代时创建一个全新的列表。
埃里克·杜米尼尔

0

不知道我的代码是否有效,有人愿意评论吗?

from math import isqrt

def isPrime(n):
    if n >= 2: # cheating the 2, is 2 even prime?
        for i in range(3, int(n / 2 + 1),2): # dont waste time with even numbers
            if n % i == 0:
                return False
    return True

def primesTo(n): 
    x = [2] if n >= 2 else [] # cheat the only even prime
    if n >= 2:
        for i in range(3, n + 1,2): # dont waste time with even numbers
            if isPrime(i):
                x.append(i)  
    return x

def primes2(n): # trying to do this using set methods and the "Sieve of Eratosthenes"
    base = {2} # again cheating the 2
    base.update(set(range(3, n + 1, 2))) # build the base of odd numbers
    for i in range(3, isqrt(n) + 1, 2): # apply the sieve
        base.difference_update(set(range(2 * i, n + 1 , i)))
    return list(base)

print(primesTo(10000)) # 2 different methods for comparison
print(primes2(10000))

0

拥有基数的最快方法是:

import sympy
list(sympy.primerange(lower, upper+1))

如果您不需要存储它们,只需使用上面的代码而不转换为即可listsympy.primerange是生成器,因此不占用内存。


请在您的答案正文中说明为什么这样做是必要的,以及它带来了哪些改进以使其看起来很有意义。
dmitryro
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.