代码之家  ›  专栏  ›  技术社区  ›  Srikar Appalaraju Tonetel

埃拉托舍内斯的筛子-寻找素数巨蟒

  •  61
  • Srikar Appalaraju Tonetel  · 技术社区  · 14 年前

    只是澄清一下,这不是一个家庭作业问题:)

    我想为我正在构建的数学应用程序找到素数 Sieve of Eratosthenes 接近。

    我已经用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)
    
    12 回复  |  直到 14 年前
        1
  •  106
  •   nnyby    5 年前

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

    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

        2
  •  11
  •   Saurabh Rana    11 年前
    def eratosthenes(n):
        multiples = []
        for i in range(2, n+1):
            if i not in multiples:
                print (i)
                for j in range(i*i, n+1, i):
                    multiples.append(j)
    
    eratosthenes(100)
    
        3
  •  7
  •   Glenn Maynard    14 年前

    要从数组(列表)的开头删除,需要向下移动该数组之后的所有项。这意味着以这种方式从前面开始删除列表中的每个元素是一个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
    
        4
  •  4
  •   MrHIDEn    6 年前

    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))
    
        5
  •  2
  •   Paul Gardiner    10 年前

    我意识到这并不能真正回答如何快速生成素数的问题,但也许有些人会发现这个替代方法很有意思:因为python通过生成器提供了惰性的计算,所以可以按照下面的说明来实现eratosthenes的sieve:

    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 block将在屏幕外显示backtrace并推送您要查看的实际输出。

        6
  •  2
  •   Ajay    8 年前

    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毫秒
    • 6:0.26秒
    • 7:3.1秒
        7
  •  1
  •   user3917838 user3917838    9 年前

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

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

        8
  •  1
  •   SilentDirge    8 年前

    我的实现:

    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
    
        9
  •  1
  •   Jules May    7 年前

    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
    

    如果你想知道的话,我也写了 素数() __iter__ __next__ ),它以几乎相同的速度运行。我也很惊讶!

        10
  •  1
  •   FooBar167    7 年前

    我喜欢裸体是因为速度。

    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]
    

    %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)
    
        11
  •  1
  •   Tom Russell    6 年前

    我想应该可以简单地使用空列表作为循环的终止条件,并得出以下结论:

    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
    
        12
  •  1
  •   nhern121    6 年前
    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]
    
        13
  •  1
  •   Pythoscorpion    5 年前

    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)))
    
        14
  •  1
  •   storm125    5 年前

    使用一点 numpy

    有两个关键特性值得注意

    • 切出倍数 i 只为 直到根 n
    • 设置倍数 False x[2*i::i] = False 比显式python for循环快得多。

    这两个明显加快了代码的速度。对于100万以下的限制,没有可感知的运行时间。

    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)))
    
        15
  •  1
  •   Madiyar    5 年前

    我能想到的最快的实现:

    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