打印给定范围内的所有质数,算法太慢..如何改进? (代码挑战)

printing all prime numbers in given range, algorithm too slow.. how to improve? (code-challenge)

这是一个关于代码挑战的问题,请不要提供太多代码。我想尽可能自己解决这个问题。

我最近开始参加代码挑战,并将其与学习相结合 Python(我白天是前端 javascript 开发人员 ;))。到目前为止一切顺利,我相信这是学习一门新语言的最佳方式(至少对我而言)。

我目前遇到了一个挑战,要求我打印给定范围内的所有质数,这一切都是通过简单的 Stdin 和 Stdout 完成的。

到目前为止我已经尝试了两种方法,两种方法都有效但速度太慢.. 下面是 link 和我目前最快实现的代码。也许我遗漏了一些非常明显的东西,导致我的 python 脚本变慢。目前,范围为 1, 100000

的单个测试用例需要 1.76s

http://ideone.com/GT6Xxk(你也可以在这里调试脚本)

from sys import stdin
from math import sqrt, ceil

next(stdin) # skip unecessary line that describes the number of test cases

def is_prime(number):
    initial_divider = sqrt(number)

    if number == 2:
      return True
    elif number % 2 == 0 or int(initial_divider) == initial_divider:
      return False

    for divider in range(ceil(initial_divider), 1, -1):
        if number % divider == 0:
            return False

    return True

for line in stdin:
    low, high = [int(x) for x in line.split(' ')]
    primes = [number for number
                     in range(max(low, 2), high+1)
                     if is_prime(number)]

    for prime in primes:
        print (prime)
    print('')

'assignment'/挑战的描述如下:

Input

The input begins with the number t of test cases in a single line (t<=10). In >each of the next t lines there are two numbers m and n (1 <= m <= n <= >1000000000, n-m<=100000) separated by a space.

Output

For every test case print all prime numbers p such that m <= p <= n, one number >per line, test cases separated by an empty line.

更新 1:我清理了最后一个块的逻辑,其中完成了素数的收集和打印:

for line in stdin:
    low, high = [int(x) for x in line.split(' ')]
    for number in range(max(low, 2), high+1):
        if is_prime(number):
            print (number)
    print('')

将列表理解更改为生成器,脚本将 运行 更快。

for number in range(max(low, 2), high+1):
    if is_prime(number):
        yield number

1) 可能是控制台IO为主,打印输出。我更改了输出,因此它使用生成器来收集素数,将数字转换为字符串,并用换行符连接数字。这应该在列表构建中节省一些内存,并将一些 Python 列表迭代推入 Python 运行 时间。这使得在我的 PC 上进行不科学的仓促测试时速度提高了约 30%,但在 ideone 上并没有太大区别。 (这可能是因为我在 Python 2 中将它设置为 运行,它有一些非常不同的 iteration/list/generator 工作原理,但在 ideone 上使用 Python 3)。

2) 你每次都运行 if number == 2: return True 测试;在前 100,000 个数字中,大多数不是 2。我在打印其余素数之前提取它以打印 2。非常小的改动,不值得。

3) 你的范围计数 down - range(ceil(initial_divider), 1, -1) - 这真的很奇怪。一个数字除以 3 的可能性比除以 19 的可能性要大得多。每隔 3 个数字除以 3,只有第 19 个数字除以 19。因此对于函数的 quick-return,请尝试使用 small首先是分隔线,对吧?我将它设置为计数 up。明显的速度提升,我希望它仍然有效。

这是原始 运行时间的 ~50%,是在一种随意且完全无法比较的情况下。代码现在看起来像这样:

from sys import stdin
from math import sqrt, ceil

next(stdin) # skip unecessary line

def is_prime(number):
    initial_divider = sqrt(number)

    if number % 2 == 0 or int(initial_divider) == initial_divider:
      return False

    for divider in range(2, ceil(initial_divider)+1):
        if number % divider == 0:
            return False

    return True

for line in stdin:
    low, high = [int(x) for x in line.split(' ')]
    primes = '\n'.join(str(number) for number
                     in range(max(low, 3), high+1)
                     if is_prime(number))

    if low <= 2: print(2)
    print (primes)
    print('')
def PrimesBelow(limit):
    np=set()
    p=[2]
    for i in xrange(1,limit+1,2):
            if i == 1: continue
            if i in np: continue
            beg=2 if i % 2 == 0 else 0
            for j in xrange(beg,int(limit)+1,i):
                    np.add(j)
            p.append(i)
    return i

LetzerWille 是对的。上面的函数将 return 下面的素数列表 (limit)。我认为这个函数 运行 比检查每个数字是否为素数更快,因为这个函数将删除每个数字的 乘法 并将它们添加到 (np)。 旁注:此函数将仅测试奇数。

在像 C 或 C++ 这样的语言中,SPOJ PRIME 1 问题可以很容易地通过蛮力解决,即通过编写代码在不到一秒的时间内筛选所有数字,直到 1000,000,000,从而保持低于时限。甚至可能在 Java、C# 或 Delphi 中。但是,如果在 Python 中完全有可能,那么它可能非常困难,需要一些严肃的 fu。

但是请注意,SPOJ PRIME 1 并不要求筛选十亿个数字;它要求筛选几个不超过 100001 个数字的小范围,并且它可能只查询少数几个范围。假设范围数是 100(可能少得多),平均宽度是 100000(可能少得多),那么仍然只有 10,000,000 个数字。在这种情况下筛选全部 10 亿项需要做两个数量级的工作,这就是为什么 SPOJ PRIME 1 能够如此精确地剔除谷壳,尽管专家们使用的语言范围很广。

因此诀窍是只做要求的事情——筛选作为输入提供的范围。即使是最简单、直接的代码也可以节省大量时间(C++:总共大约一毫秒)。原理和我对challenge of drawing 100 random primes from a range with an upper bound of 1,000,000,000的回答完全一样,同样的解决方法。关键是编写一个可以筛选给定范围 (window) 的函数,而不必筛选下面的所有数字。

此外,关于如何打败SPOJ PRIME 1的问题已经被问过无数次了,给出的答案仍然有效。一小部分:

  • How do I efficiently sieve through a selected range for prime numbers?
  • Efficient algorithm to get primes between two large numbers
  • Generating prime numbers between m and n
  • SPOJ PRIME1 : TLE
  • Segmented Sieve of Erastothenes C++ SPOJ
  • Spoj PRIME1 using sieve of eratosthenes (in C)
  • ...

简单的改进。看到这个简单的代码很尴尬。我的又长又慢 :( ... 但我学到了很多 :)

同时添加用于测量时间的简单函数 mt()

def PrimesBelow(limit):
    np=set()
    p=[2]
    for i in range(3,limit+1,2):
            if i in np: continue
            beg = i*i
            for j in range(beg,int(limit)+1,i):
                    np.add(j)
            p.append(i)
    return p

def mt(n):
    import time
    t = time.time()
    pr = PrimesBelow(n)
    print("#-Primes: {}, Time: {}".format(len(pr), round(time.time()-t, 4)))
    return pr

pr = mt(100000000)

在 16GB 的 i7-3770 上大约需要 49 秒

这将是执行次数更少的优化代码,它可以在一秒钟内计算并显示10000个素数。 它使用素数 属性 * 如果一个数不能被小于它的 平方根 的数整除那么它就是质数。 * 我们可以在 35 次迭代内结束循环,而不是检查到最后(意味着 1000 次迭代以确定 1000 是否为素数) * break 如果它在开始时被任何数字除以循环(如果它是偶数循环将在第一次迭代时中断,如果它可以被 3 整除然后 2 次迭代)所以我们迭代直到只为质数结束

记住一件事,你仍然可以使用 属性 优化迭代 *如果一个数不能被小于它的质数整除,那么它就是质数,但代码会太大,我们必须跟踪计算出的素数,也很难找到特定数字是否为素数,因此这将是最佳逻辑或代码

import math
number=1
count = 0



while(count<10000):
    isprime=1
    number+=1
    for j in range(2,int(math.sqrt(number))+1):
        if(number%j==0):
            isprime=0   
            break
    if(isprime==1):
        print(number,end=" ")
        count+=1    
print("\nCount "+str(count))