我正在尝试Project Euler的问题10,这是2,000,000以下所有素数的总和。我尝试使用Python实现Erasthotenes的Sieve,我编写的代码完全适用于10,000以下的数字。
但是,当我试图找到较大数字的素数总和时,代码运行时间太长(找到最多100,000个素数的总和需要315秒)。该算法显然需要优化。
是的,我查看了本网站上的其他帖子,例如Fastest way to list all primes below N,但那里的解决方案对代码的工作原理(我还是初学程序员)的解释很少,所以我无法实际上是向他们学习。
有人可以帮助我优化我的代码,并清楚地解释它是如何工作的吗?
这是我的代码:
primes_below_number = 2000000 # number to find summation of all primes below number
numbers = (range(1, primes_below_number + 1, 2)) # creates a list excluding even numbers
pos = 0 # index position
sum_of_primes = 0 # total sum
number = numbers[pos]
while number < primes_below_number and pos < len(numbers) - 1:
pos += 1
number = numbers[pos] # moves to next prime in list numbers
sum_of_primes += number # adds prime to total sum
num = number
while num < primes_below_number:
num += number
if num in numbers[:]:
numbers.remove(num) # removes multiples of prime found
print sum_of_primes + 2
正如我之前所说,我是编程新手,因此对任何复杂概念的全面解释将深表感谢。谢谢。
答案 0 :(得分:1)
首先,从列表中删除数字将非常缓慢。而不是这个,列一个列表
primes = primes_below_number * True
primes[0] = False
primes[1] = False
现在在您的循环中,当您找到素数p时,将primes[k*p]
更改为False
以获取所有合适的k。 (你实际上不会成倍增加,当然,你不断增加p。)
最后,
primes = [n for n i range(primes_below_number) if primes[n]]
这应该会更快。
其次,一旦找到大于primes_below_number
的平方根的素数,就可以停止查看,因为复合数必须具有不超过其平方根的素数因子。
答案 1 :(得分:1)
正如您所见,有多种方法可以在Python中实现比您的代码更高效的Erasthotenes Sieve。我不想让你混淆花哨的代码,但我可以展示如何加快代码的速度。
首先,搜索列表并不快,从列表中删除元素的速度更慢。但是,Python提供了一种集合类型,它在执行这两种操作时非常有效(尽管它确实比简单列表嚼了更多的RAM)。令人高兴的是,修改代码以使用集合而不是列表很容易。
另一个优化是,我们不必检查素数因素,直到primes_below_number
,我已在下面的代码中将其重命名为hi
。仅仅到hi
的平方根就足够了,因为如果一个数是复合的,它必须有一个小于或等于其平方根的因子。
我们不需要保持总计的总和。最后使用Python内置的sum()
函数来做到这一点比较好,它以C速度运行,所以它比逐个添加的速度快得多。 Python速度。
# number to find summation of all primes below number
hi = 2000000
# create a set excluding even numbers
numbers = set(xrange(3, hi + 1, 2))
for number in xrange(3, int(hi ** 0.5) + 1):
if number not in numbers:
#number must have been removed because it has a prime factor
continue
num = number
while num < hi:
num += number
if num in numbers:
# Remove multiples of prime found
numbers.remove(num)
print 2 + sum(numbers)
您应该会发现此代码会在几秒钟内运行;我的2GHz单核机器需要大约5秒钟。
您会注意到我已移动评论,以便他们超出他们评论的界限。这是Python中的首选风格,因为我们更喜欢短行,而且内联注释往往会使代码看起来混乱。
可以对内部while
循环进行另一个小优化,但我让你自己解决这个问题。 :)
答案 2 :(得分:0)
尝试使用numpy,应该让它更快。用xrange替换范围,它可能对你有帮助。
答案 3 :(得分:0)
以下是您的代码优化:
import itertools
primes_below_number = 2000000
numbers = list(range(3, primes_below_number, 2))
pos = 0
while pos < len(numbers) - 1:
number = numbers[pos]
numbers = list(
itertools.chain(
itertools.islice(numbers, 0, pos + 1),
itertools.ifilter(
lambda n: n % number != 0,
itertools.islice(numbers, pos + 1, len(numbers))
)
)
)
pos += 1
sum_of_primes = sum(numbers) + 2
print sum_of_primes
这里的优化是因为:
itertools
可以使事情变得更快,因为我们使用迭代器而不是多次遍历整个列表。另一个解决方案是不删除部分列表但禁用它们就像@saulspatz所说的那样。
这是我能找到的最快方式:http://www.wolframalpha.com/input/?i=sum+of+all+primes+below+2+million
这是布尔方法:
import itertools
primes_below_number = 2000000
numbers = [v % 2 != 0 for v in xrange(primes_below_number)]
numbers[0] = False
numbers[1] = False
numbers[2] = True
number = 3
while number < primes_below_number:
n = number * 3 # We already excluded even numbers
while n < primes_below_number:
numbers[n] = False
n += number
number += 1
while number < primes_below_number and not numbers[number]:
number += 1
sum_of_numbers = sum(itertools.imap(lambda index_n: index_n[1] and index_n[0] or 0, enumerate(numbers)))
print(sum_of_numbers)
这在几秒钟内执行(在我的2.4GHz机器上花了3秒钟)。
答案 4 :(得分:0)
您可以改为存储一个布尔值数组,而不是存储数字列表。这种位图的使用可以被认为是一种实现集合的方法,它适用于密集集合(成员的值之间没有很大的差距)。
An answer on a recent python sieve question使用此实现python-style。事实证明,很多人已经实施了一个筛子,或者他们认为是筛子的东西,然后来问SO为什么它很慢。 :P如果你想要更多的阅读材料,请查看其中一些相关问题边栏。
查找包含布尔值的元素,该布尔值表示数字是否在集合中,这很容易且非常快。 array[i]
是一个布尔值,如果i
在集合中,则为true,否则为false。内存地址可以通过一次添加直接从i
计算。
(我正在掩饰这样一个事实:布尔数组可能与每个元素的整个字节一起存储,而不是为每个元素使用每个比特的更有效的实现。任何体面的筛子都将使用位图。)
从集合中删除数字就像设置array [i] = false一样简单,无论以前的值如何。没有搜索,没有比较,没有跟踪发生的事情,只是一次内存操作。 (好吧,两个用于位图:加载旧字节,清除正确的位,存储它。存储器是字节可寻址的,但不能位寻址。)
基于位图的筛子的一个简单优化是甚至不存储偶数字节,因为只有一个偶数素数,我们可以特殊情况下将内存密度加倍。然后,i
的成员资格状态保存在array[i/2]
中。 (对于计算机来说,除以2的幂是很容易的。其他值要慢得多。)
一个SO问题: Why is Sieve of Eratosthenes more efficient than the simple "dumb" algorithm?有许多关于筛子的好东西的链接。 This one in particular有一些很好的讨论,用文字而不仅仅是代码。 (没关系,它正在谈论一个看起来像筛子的常见Haskell实现,但实际上并非如此。他们称之为图中的“不忠”筛子,等等。)
对于某些用途,discussion on that question提出了试验除法可能比大型筛子更快的观点,因为清除每个素数的所有倍数的位会以缓存不友好的模式触及大量内存。如今,CPU比内存快得多。