素数的总和:使用发电机与无发电机[PYTHON]

时间:2017-10-16 15:29:25

标签: python generator primes

项目欧拉第10个问题: 找出200万以下所有素数的总和。 我想出了两个解决方案:

使用发电机功能

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)

W / O GENERATOR

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分钟 !!!

为什么会这样?认为由于步骤较少,发电机的性能会更好。

2 个答案:

答案 0 :(得分: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秒。

如果您对查找质数的更有效算法感兴趣,请查看Eratosthenes筛。  https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes

答案 1 :(得分:0)

这是您的生成器代码的重做,几乎是非生成器代码的两倍。与@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

两种解决方案之间的区别是,一种仅使用质数作为试验除数(以存储成本为代价),另一种使用奇数(伪素数)作为试验除数(无存储成本)。在时间上,少做试验的人会获胜。