【问题标题】:Summation of primes : Using Generator vs w/o Generator素数求和:使用生成器与不使用生成器
【发布时间】:2017-10-16 15:29:25
【问题描述】:

欧拉计划第十题: 求两百万以下的所有素数之和。 好吧,我想出了两个解决方案:

使用生成器函数

def gen_primes():
    n = 2
    primes = []
    while True:
        for p in primes:
            if n % p == 0:
                break
        else:
            primes.append(n)
            yield n
        n += 1
flag = True
sum=0
p=gen_primes()
while flag:
    prime=p.__next__()
    if prime > 2000000:
        flag = False
    else:
        sum+=prime
print(sum)

无发电机

def prime(number):
    if number ==1:
        return -1
    else:
        for a in (range(1,int(number**0.5)+1))[::2]:
            if number%a==0 and a!=1:
                return False
        else:
            return True    
count=2
for i in (range(1,2000000))[::2]:
    if prime(i)==True and i!=1:
        count+=i
    else:
        continue
print(count)

令人惊讶的是,后者(w/o g)需要 7.4 秒,而前者(使用 g)大约需要 10 分钟 !!!

为什么会这样?认为生成器会因为更少的步骤而表现得更好。

【问题讨论】:

  • primes.append(n) 那一定是需要时间的事情。你不需要那个。
  • 最高效的解决方案是 Erathothenes 的筛子。查一下。
  • 这 2 种解决方案几乎不等价;在顶部,您使用while True,它比for 循环慢。此外,您探索的数字范围更有限,因此速度更快也就不足为奇了
  • 为什么要使用while 循环并显式调用__next__?只需使用 for 循环,即 整个点

标签: python generator primes


【解决方案1】:

您的生成器正在做很多不必要的工作。当它只需要检查直到n 的平方根的素数时,它会检查直到n 的所有素数。这是您的生成器函数的修改版本,删除了不必要的工作:

from time import time

t = time()


def gen_primes():
    primes = []
    yield 2
    n = 3

    while True:
        is_prime = True
        upper_limit = n ** .5  
        for p in primes:
            if n % p == 0:
                is_prime = False
                break
            elif p > upper_limit:
                break
        if is_prime:
            primes.append(n)
            yield n
        n += 2  # only need to check divisibility by odd numbers


sum = 0
for prime in gen_primes():
    if prime > 2_000_000:
        break
    else:
        sum += prime

print("time:", time() - t)
print(sum)

这在我的电脑上需要 2.3 秒。没有生成器的版本在我的电脑上需要 4.8 秒。

如果您对寻找素数的更高效算法感兴趣,请查看埃拉托色尼筛法。 https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes

【讨论】:

    【解决方案2】:

    这是您的生成器代码的返工,其速度几乎是非生成器代码的两倍。类似于@DanielGiger 的解决方案(+1),但尝试更简单,可能更快。它使用多重平方而不是单个平方根(可能会或可能不会胜出)并且不需要布尔标志:

    def gen_primes():
        yield 2
        primes = [2]
    
        number = 3
    
        while True:
    
            for prime in primes:
                if prime * prime > number:
                    yield number
                    primes.append(number)
                    break
    
                if number % prime == 0:
                    break
    
            number += 2
    
    total = 0
    generator = gen_primes()
    
    while True:
        prime = next(generator)
    
        if prime > 2_000_000:
                break
    
        total += prime
    
    print(total)
    

    这是对非生成器代码的等效修改,它也试图变得更简单,而且速度更快。我的经验法则是将 2 和更少作为特殊情况处理,然后编写一个干净的奇数除数检查:

    def is_prime(number):
        if number < 2:
            return False
    
        if number % 2 == 0:
            return number == 2
    
        for divisor in range(3, int(number**0.5) + 1, 2):
            if number % divisor == 0:
                return False
    
        return True
    
    total = 2
    
    for i in range(3, 2_000_000, 2):
        if is_prime(i):
            total += i
    
    print(total)
    

    此外,您的代码会生成整个数字范围,然后丢弃偶数 - 我们可以使用 range() 的第三个参数首先只生成奇数。

    最后,生成器的使用与您的两种解决方案的一般性能无关。您可以将您的第二个解决方案重写为生成器:

    def gen_primes():
        yield 2
    
        number = 3
    
        while True:
    
            for divisor in range(3, int(number**0.5) + 1, 2):
                if number % divisor == 0:
                    break
            else:  # no break
                yield number
    
            number += 2
    

    两种解决方案之间的区别在于,一种仅使用素数作为试除数(以存储为代价),另一种使用奇数(伪素数)作为试除数(无存储成本)。从时间上看,试赛次数少的一方获胜。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-24
      • 2017-05-30
      • 1970-01-01
      • 2019-10-22
      • 1970-01-01
      • 2015-05-25
      • 2020-12-11
      相关资源
      最近更新 更多