我的问题直接来自CS圈子网站。这是this页面底部的最后一个问题,名为'Primed for Takeoff'。基本纲要是他们想要一个1,000,001长度的列表,其中如果索引是素数,则每个项目的索引为True,而如果它不是素数,则每个项目的索引为False。
因此,例如,isPrime [13]为True。 isPrime [14]是假的。
列表'isPrime'的第一点看起来像:
isPrime = [False, False, True, True, False, True, False, True, False, False, ...]
我的问题是它们有7秒的时间限制。我有一个下面的工作代码,数字较小,为101,用于调试目的。当我将它提升到所需的1,000,001列表长度时,它需要的时间太长,几分钟后我终于杀死了程序。 这是我的工作(长度为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)
然后我发现了here。它有一个更快的运行时间,但只输出一个素数列表,而不是一个n长度列表,list [n]返回True表示素数,否则返回false。
我不确定这个第二位代码是否真的能够在7秒内创建长度为1,000,001的主要列表,但这是我在研究中找到的最相关的东西。
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圈子似乎很常用,但我无法为Python找到这个的工作版本。希望这对某人来说很容易解决。
这个问题与this one不同,因为我不是试图只快速创建一个素数列表,而是创建一个从0到n的所有正整数的列表,这些正整数由True标记为素数而非素数由False标记为。
答案 0 :(得分:9)
编辑:意识到SO上有很多优化,但很少有其他人为主筛算法解释它们,因此很难让初学者或第一次算法创建者接近它们。所有解决方案都将在python中,以便在速度和优化的同一页面上。这些解决方案将逐渐变得更快,更复杂。 :)
香草溶液
def primesVanilla(n):
r = [True] * n
r[0] = r[1] = False
for i in xrange(n):
if r[i]:
for j in xrange(i+i,n,i):
r[j]=False
return r
这是Sieve非常简单的实现。在继续之前,请确保您了解上面发生的事情。唯一需要注意的是你在i + i而不是i开始标记非素数,但这是显而易见的。 (因为你认为我本身就是一个素数)。为了使测试公平,所有数字都将列在 2500万的列表中。
real 0m7.663s
user 0m7.624s
sys 0m0.036s
次要改进1(平方根):
我会尝试按照直接向不太直接的变化对它们进行排序。注意我们不需要迭代到n,而只需要上升到n的平方根。原因是n下的任何复合数必须具有低于或等于n的平方根的素因子。当你手工筛网时,你会注意到所有未被发现的" n的平方根上的数字默认为素数。
另一个注意事项是,当平方根变成一个整数时,你必须要小心一点,所以你应该在这种情况下添加一个,以便它覆盖它。 IE,在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)等。)
def primes3(n):
r = [True] * n
r[0] = r[1] = False
for i in xrange(4,n,2):r[i]=False
for i in xrange(3,int(n**0.5+1),2):
if r[i]:
for j in xrange(i*i,n,2*i):
r[j]=False
return r
real 0m2.916s
user 0m2.872s
sys 0m0.040s
酷的改进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
作业加速(Padraic Cunningham) 请注意,我们为所有True分配一个数组,然后将half(均匀)设置为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内部,我都无法找到任何有意义的速度提升。:(
答案 1 :(得分:3)
我看到的第一件事是你生成初始列表(循环和追加)的方式是低效且不必要的。您只需添加列表,而不是循环和追加每个元素。
我看到的第二件事是您正在进行的类型检查是不必要的,函数调用很昂贵,您可以重构以完全避免这种情况。
最后,我认为你可以在任何筛选实现中获得的“大事”是利用切片分配。你应该在一次点击而不是循环中划掉所有因素。例如:
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
注意我还有其他一些技巧:
在我糟糕的动力不足的macbook上,这段代码可以在大约75毫秒内生成1,000,001列表:
>>> timeit primes(1000001)
10 loops, best of 3: 75.4 ms per loop
答案 2 :(得分:2)
python2和3中的一些时间显示wim的方法明显更快,可以通过创建列表的方式进一步优化:
private void Open_Image_btn_Click(object sender, EventArgs e)
{
using (OpenFileDialog dlg = new OpenFileDialog())
{
dlg.Title = "Open Image";
if (dlg.ShowDialog() == DialogResult.OK)
{
pictureBox1.Image = new Bitmap(dlg.FileName);
}
}
}
Python2时间:
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
python3的计时,使用相同的函数只改变范围:
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
可以通过使用范围/ xrange而不是切片来进一步优化:
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
Python3它打破了很好的一块:
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
对于使用xrange的python2也一样:
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
也应该有效:
(((n - 3 * i) // (2 * i)) + 1)
速度稍快一点:
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
您也可以从3和第2步开始:
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
Python2:
In [2]: timeit primes_wim_opt_2(100000)
1000 loops, best of 3: 1.10 ms per loop