Python 中的高效埃拉托斯特尼筛法

问题描述 投票:0回答:5

#Python 中这段非常简短的代码尝试模拟前 N 个自然数的“埃拉托斯特尼筛法”,并具有 (0) 脚本简短的约束; (1) 最小化“if 语句”和“for/while 循环”; (2) CPU 时间方面的效率。

import numpy as np
N = 10**5
a = np.array(range(3,N,2))
for j in range(0, int(round(np.sqrt(N),0))):
    a[(a!=a[j]) & (a%a[j] == 0)] = 0
    a = a[a!=0]
a = [2]+list(a)

在 Intel Core I5 上,它返回第一个中的质数:

  • 0.03 秒内 N = 100,000;
  • N = 1,000,000 在 0.63 秒内;
  • N = 10,000,000 在 22.2 秒内。

有人愿意在上述限制内分享 CPU 时间更高效的代码吗?

python numpy sieve-of-eratosthenes
5个回答
18
投票

埃拉托色尼的实际 NumPy 筛看起来像这样:

def sieve(n):
    flags = numpy.ones(n, dtype=bool)
    flags[0] = flags[1] = False
    for i in range(2, n):
        # We could use a lower upper bound for this loop, but I don't want to bother with
        # getting the rounding right on the sqrt handling.
        if flags[i]:
            flags[i*i::i] = False
    return numpy.flatnonzero(flags)

它维护一个“可能是素数”标志的数组,并直接取消设置与素数倍数对应的标志,而不需要测试整除性,特别是对于不能被当前正在处理的素数整除的数字。

您所做的是试除法,您只需遍历并测试数字是否可以被候选除数整除。即使很好地实现了试除,也需要比筛子进行更多的操作,并且操作成本更高。您的实现所做的工作甚至比这更多,因为它考虑非素数候选除数,并且因为它不断对它应该已经知道是素数的数字执行整除性测试。


3
投票

我决定尝试一下,并创建了一个由 @user2357112 发布的进一步优化的 NumPy 版本,支持 Monica,它使用 Numba JIT 来加快速度。

import numba
import numpy
import timeit
import datetime 

@numba.jit(nopython = True, parallel = True, fastmath = True, forceobj = False)
def sieve (n: int) -> numpy.ndarray:
    primes = numpy.full(n, True)
    primes[0], primes[1] = False, False
    for i in numba.prange(2, int(numpy.sqrt(n) + 1)):
        if primes[i]:
            primes[i*i::i] = False
    return numpy.flatnonzero(primes)

if __name__ == "__main__":
    
    timestart = timeit.default_timer()
    print(sieve(1000000000))
    timestop = timeit.default_timer()
    timedelta = (timestop - timestart)
    print(f"Time Elapsed: {datetime.timedelta(seconds = timedelta)}")

else:
    pass

在我的笔记本电脑上,我在

1e9
秒内筛出了前 10 亿 (
0:00:10.378686
) 个自然数中的素数。 JIT 在这里至少提供了一个数量级的性能;在撰写本文时,下一个最快的答案需要
0:01:27.059963
分钟。遗憾的是,我在这个系统 (Mac) 上没有 Nvidia GPU 和 Cuda,否则我会使用它。


2
投票

1.94 秒,10.000.000

def sieve_eratosthene(limit):

    primes = [True] * (limit+1)
    iter = 0

    while iter < limit**0.5 :
        if iter < 2:
            primes[iter]= False

        elif primes[iter]:
            for i in range(iter*2, limit+1, iter):
                primes[i] = False

        iter+=1

    return(x for x in range(limit+1) if primes[x])

1
投票

这是使用 NumPy 执行此操作的简单方法。它有点类似于OP的索引而不是在主循环内再次循环的想法,但没有除法检查,只有切片。

它也类似于user2357112支持Monica答案,但是这个只考虑奇数,这使得它更快。

这是典型的仅奇数筛子:https://stackoverflow.com/a/20248491/8094047.

  1. 创建一个大小为 n 的布尔数组。
  2. 循环奇数直至 n 的平方根。
    注意:数字可互换用作索引
  3. 将合数(素数的倍数)标记为假。

最后我们有一个 bool 数组,我们可以用它来检查 n 以下的数字是否是质数(偶数除外,您可以在没有数组的情况下检查它们,使用 & 1 左右)

平均。 n = 20000000 的时间:0.1063s

import numpy as np
n = 20000000

isprime = np.ones(n, dtype=np.bool)

# odd only sieve
i = 3
while (i * i < n):
    if isprime[i]:
        isprime[i * i:n:2 * i] = False
    i += 2

# test
print(isprime[97]) # should be True

0
投票

约 5 秒内 10 亿个素数

这使用 Numpy 并跳过切片:

def make_sieve(size): 
    sieve_time = time.time() 
    print(f"Creating new sieve of {size:,} primes.")
    limit = int(1 + size**0.5)+2
    p = np.ones(size,dtype=np.int8)
    p[0] = p[1] = 0
    s = p[4::2]
    s[:] = 0   # clear 4,6,8,10
    s = p[9::3]
    s[:] = 0   # clear 9,12,15...
    for i in range(5,limit,6): # all  6k-1
        h = i+2             # 6k+1
        if p[i]:
            s = p[3*i::2*i]  # Get a view 
            s[:] = 0         # Clear it
        if p[h]:
            s = p[3*h::2*h]  # Get a view 
            s[:] = 0         # Clear it
    print(f" Make sieve for {len(p):,} took {str(timedelta(seconds=time.time()-sieve_time))} ")
    return p   

我计算素数并查找或显示素数,例如:

p = make_sieve(n)
c =  np.sum(p[1:n+1])
         
print(f"From 1 to {n:,} there are {c:,}  primes") 

def get_primes(x,start=0,size=200): 
    return [i for i in range(start,start+size+1) if x[i]]
  
def isPrime(x,n): 
    return x[n] == 1

输出如下:

Creating new sieve of 100,000,000 primes. 
Make sieve for 100,000,000 took 0:00:00.475777  
Count primes to: >1000000000 
Creating new sieve of 1,000,000,000 primes.  
Make sieve for 1,000,000,000 took 0:00:05.382128  
From 1 to 1,000,000,000 there are 50,847,534  primes
© www.soinside.com 2019 - 2024. All rights reserved.