Python基础质数生成器

10

我想让大家对我的质数生成器提供一些反馈,比如它是否可以使用,是否使用了过多的资源等。它不使用任何库,相当简单,并且反映了我目前的编程技能水平,所以请毫不犹豫地提出建议,因为我想学习。

def prime_gen(n):

    primes = [2]
    a = 2 

    while a < n:

        counter = 0 

        for i in primes:
            if a % i == 0:
                counter += 1

        if counter == 0:
            primes.append(a)
        else:
            counter = 0

        a = a + 1

    print primes

1
你可以看一下这个问题:https://dev59.com/6XRB5IYBdhLWcg3wn4fc - ashwinjv
如果数字是9,这段代码能正常工作吗? 计数器变量的目的是什么? 附注:a = a + 1 可以简化为 a += 1 - pygeek
1
特别是在被接受的答案中的埃拉托斯特尼筛法实现(https://dev59.com/6XRB5IYBdhLWcg3wn4fc#568618)。它确实涉及使用生成器。您可以在此处获取有关生成器的更多信息:https://dev59.com/yXVC5IYBdhLWcg3woSpW#231855 - ashwinjv
查看这个问题:在Python中查找前N个质数在Python中查找前1000个质数的总和 - Jose Ricardo Bustos M.
1
你可以使用生成器算法来代替返回列表。这样可以节省内存。此外,生成小于某个数字的质数的首选算法是埃拉托斯特尼筛法。 - Jayanth Koushik
在我看来,如果你按照这个链接的所有步骤去做,你可以学到很多东西:https://dev59.com/Gl0a5IYBdhLWcg3w6sVk#51133570 - jimifiki
10个回答

4

有一些常见的优化:

示例:

def prime(x):
    if x in [0, 1]:
        return False
    if x == 2:
        return True
    for n in xrange(3, int(x ** 0.5 + 1)):
        if x % n == 0:
            return False
    return True
  • 覆盖基本情况
  • 仅迭代到n的平方根

上面的例子没有生成质数,而是测试它们。你可以将相同的优化适用于你的代码 :)

我在以下问题的答案中找到了一种更有效率的Python算法(使用埃拉托色尼筛选法):

Python中的简单质数生成器

我自己对筛选法进行了改编:

from itertools import islice


def primes():
    if hasattr(primes, "D"):
        D = primes.D
    else:
        primes.D = D = {}

    def sieve():
        q = 2
        while True:
            if q not in D:
                yield q
                D[q * q] = [q]
            else:
                for p in D[q]:
                    D.setdefault(p + q, []).append(p)
                del D[q]

            q += 1

    return sieve()


print list(islice(primes(), 0, 1000000))

在我的硬件上,我可以相当快地生成前一百万个质数(考虑到这是用Python编写的):

prologic@daisy
Thu Apr 23 12:58:37 
~/work/euler
$ time python foo.py > primes.txt

real    0m19.664s
user    0m19.453s
sys 0m0.241s

prologic@daisy
Thu Apr 23 12:59:01 
~/work/euler
$ du -h primes.txt
8.9M    primes.txt

1
在测试时最好明确检查2,然后迭代xrange(3, int(x ** 0.5 + 1), 2)。 没有必要检查所有偶数。 - wim

2

以下是从C#版本改编的生成质数的标准方法,参考自:最优雅的生成质数的方式

def prime_gen(n):

    primes = [2]

    # start at 3 because 2 is already in the list
    nextPrime = 3

    while nextPrime < n:

        isPrime = True

        i = 0

        # the optimization here is that you're checking from
        # the number in the prime list to the square root of
        # the number you're testing for primality
        squareRoot = int(nextPrime ** .5)

        while primes[i] <= squareRoot:

            if nextPrime % primes[i] == 0:

                isPrime = False

            i += 1

        if isPrime:

            primes.append(nextPrime)

        # only checking for odd numbers so add 2
        nextPrime += 2

    print primes

1
你从这里开始:

def prime_gen(n):
    primes = [2]
    a = 2 

   while a < n:
       counter = 0 

       for i in primes:
          if a % i == 0:
              counter += 1

        if counter == 0:
            primes.append(a)
        else:
            counter = 0

        a = a + 1

    print primes

你真的需要else分支吗?不需要。

def prime_gen(n):
    primes = [2]
    a = 2 

   while a < n:
       counter = 0 

       for i in primes:
          if a % i == 0:
              counter += 1

        if counter == 0:
            primes.append(a)

        a = a + 1

    print primes

你需要计数器吗?不需要!
def prime_gen(n):
    primes = [2]
    a = 2 

   while a < n:

       for i in primes:
           if a % i == 0:
               primes.append(a)
               break 

        a = a + 1

    print primes

你需要检查i是否大于sqrt(a)吗?不需要。

def prime_gen(n):
    primes = [2]
    a = 3 

   while a < n:
       sqrta = sqrt(a+1)
       for i in primes:
           if i >= sqrta:
               break
           if a % i == 0:
               primes.append(a)
               break 

        a = a + 1

    print primes

你真的想要手动增加a吗?

def prime_gen(n):
    primes = [2]

   for a in range(3,n):
       sqrta = sqrt(a+1)
       for i in primes:
           if i >= sqrta:
               break
           if a % i == 0:
               primes.append(a)
               break 

这是一些基础的重构,应该能够自然而然地流出你的手指。
然后你测试重构后的代码,发现它有错误并修复它:
def prime_gen(n):
    primes = [2]
    for a in range(3,n):
        sqrta = sqrt(a+1)
        isPrime = True 
        for i in primes:
            if i >= sqrta:
                break
            if a % i == 0:
                isPrime = False
                break 
        if(isPrime): 
            primes.append(a)
    return primes

最后,你可以摆脱isPrime标志:
def prime_gen(n):
    primes = [2]
    for a in range(3,n):
        sqrta = sqrt(a+1)
        for i in primes:
            if i >= sqrta:
                primes.append(a)
                break
            if a % i == 0:
                break 
    return primes

现在你认为你已经完成了。然后突然有个朋友指出,对于偶数 a,你检查 i >= sqrta 是没有必要的。(类似地,对于 a mod 3 == 0 的数字,但是分支预测会提供帮助。) 你的朋友建议你先检查 a % i == 0

def prime_gen(n):
    primes = [2]
    for a in range(3,n):
        sqrta = sqrt(a+1)
        for i in primes:
            if a % i == 0:
                break 
            if i >= sqrta:
                primes.append(a)
                break
    return primes

现在你已经完成了,感激你聪明的朋友!


有其他方法可以计算前n个质数。我试图展示如何重构你的代码。请注意,对于Python 2.x,xrange比range更快,但我假设你正在使用3.x版本。 - jimifiki
在我的电脑上,当n = 100时,我的版本比原始版本快两倍。当n = 1000时,我的版本快10倍。对于n = 10,000,我的版本快60倍。对于n = 100,000,它快325倍! - jimifiki
1
如果在 i >= sqrta 之前检查 a % i == 0,代码运行速度会更快。至少在我的笔记本电脑上,当 n = 1,000,000 时,代码始终需要少 5% 的时间。 - gouravkr
@gourevkr 感谢您的检查。我正在考虑重构代码。 - jimifiki
我来这里是为了学习,结果被学校教育了 :-D 顺便说一下,在我的硬件上,我们得到的程序比被接受的答案快了7倍! - jimifiki

1
你可以使用Python的yield语句逐个生成项目。因此,您将迭代生成器并逐个获取项目,而不是一次获取所有项目。这将最小化您的资源。

以下是一个示例:

from math import sqrt
from typing import Generator


def gen(num: int) -> Generator[int, None, None]:
    if 2 <= num:
        yield 2
    yield from (
        i
        for i in range(3, num + 1, 2)
        if all(i % x != 0 for x in range(3, int(sqrt(i) + 1)))
    )


for x in gen(100):
    print(x, end=", ")

输出:

 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 

1

我对jimifiki提出的解决方案进行了改进

import math #for finding the sqare root of the candidate number
def primes(n):
  test = [3] #list of primes new candidates are tested against
  found = [5] #list of found primes, which are not being tested against
  c = 5 #candidate number, starting at five
  while c < n: #upper bound, largest candidate will be this or 1 bigger
    p = True #notes the possibility of c to be prime
    c += 2 #increase candidate by 2, avoiding all even numbers
    for a in test: #for each item in test
        if c % a == 0: #check if candidate is divisible
            p = False #since divisible cannot be prime
            break #since divisible no need to continue checking
    if p: #true only if not divisible
        if found[0] > math.sqrt(c): #is samallest in found > sqrt of c
            found.append(c) #if so c is a prime, add it to the list
        else: #if not, it's equal and we need to start checking for it
            test.append(found.pop(0)) #move pos 0 of found to last in test
  return([2] + test + found) #after reaching limit returns 2 and both lists

最大的改进是不检查偶数,并且仅在数字不可被整除时才检查平方根,当数字变大时,后者确实会增加。我们不需要检查平方根的原因是,测试列表中只包含小于平方根的数字。这是因为我们只有在到达第一个非素数且不可被测试中的任何数字整除时才添加下一个数字。这个数字总是下一个最大质数的平方,也是found中最小的数字。使用布尔值“p”感觉有点混乱,因此可能有改进的空间。

0

获取第100个质数:

import itertools
n=100
x = (i for i in itertools.count(1) if all([i%d for d in xrange(2,i)]))
print list(itertools.islice(x,n-1,n))[0]

获取100以内的质数

import itertools
n=100
x = (i for i in xrange(1,n) if all([i%d for d in xrange(2,i)]))
for n in x:
    print n

0

这是我一段时间前编写的一个相当高效的质数生成器,它使用埃拉托斯特尼筛法

#!/usr/bin/env python2.7

def primeslt(n):
    """Finds all primes less than n"""

    if n < 3:
        return []

    A = [True] * n
    A[0], A[1] = False, False

    for i in range(2, int(n**0.5)+1):
        if A[i]:
            j = i**2
            while j < n:
                A[j] = False
                j += i

    return [num for num in xrange(n) if A[num]]

def main():
    i = ''
    while not i.isdigit():
        i = raw_input('Find all prime numbers less than... ')
    print primeslt(int(i))

if __name__ == '__main__':
    main()

维基百科文章(上面有链接)比我能讲得更好,所以我建议你去阅读一下。

0

当参数为负数时,我有一些针对第一段代码的优化建议:

def is_prime(x):    
    if x <=1:
        return False
    else:
        for n in xrange(2, int(x ** 0.5 + 1)):
            if x % n == 0:
                return False
    return True
print is_prime(-3)

0

在Python中,通常最好返回一个生成器,它将返回无限的质数序列,而不是列表。

ActiveState有一个旧版Eratosthenes筛法的列表recipes

这里是其中之一,使用itertools count更新到Python 2.7,并使用了步骤参数,这在原始配方编写时不存在:

import itertools as it

def sieve():
    """ Generate an infinite sequence of prime numbers.
    """
    yield 2
    D = {}
    for q in it.count(3, 2):   # start at 3 and step by odds
        p = D.pop(q, 0)
        if p:
            x = q + p
            while x in D: x += p
            D[x] = p          # new composite found. Mark that
        else:
            yield q           # q is a new prime since no composite was found
            D[q*q] = 2*q

由于它是一个生成器,所以比生成整个列表更节省内存。由于它定位复合数,因此在计算上也更有效率。

运行此命令:

>>> g=sieve()

然后每个后续的调用都返回下一个质数:

>>> next(g)
2
>>> next(g)
3
# etc

你可以使用islice来获取在边界之间的列表(例如,从第一个质数到第X+Y个质数的列表……)。
>>> tgt=0
>>> tgt, list(it.islice(sieve(), tgt, tgt+10))
(0, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29])
>>> tgt=1000000
>>> tgt, list(it.islice(sieve(), tgt, tgt+10))
(1000000, [15485867, 15485917, 15485927, 15485933, 15485941, 15485959, 15485989, 15485993, 15486013, 15486041])

0

你也可以用这种方式在Python中将质数存储到字典中

def is_prime(a):
    count = 0
    counts = 0
    k = dict()
    for i in range(2, a - 1):
        k[count] = a % i
        count += 1
    for j in range(len(k)):
        if k[j] == 0:
            counts += 1

    if counts == 0:
        return True
    else:
        return False


def find_prime(f, g):
    prime = dict()
    count = 0
    for i in range(int(f), int(g)):
        if is_prime(i) is True:
            prime[count] = i
            count += 1
    return prime

a = find_prime(20,110)
print(a)

{0: 23, 1: 29, 2: 31, 3: 37, 4: 41, 5: 43, 6: 47, 7: 53, 8: 59, 9: 61, 10: 67, 11: 
71, 12: 73, 13: 79, 14: 83, 15: 89, 16: 97, 17: 101, 18: 103, 19: 107, 20: 109}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接