【问题标题】:Quickly determine if a number is prime in Python for numbers < 1 billion快速确定 Python 中小于 10 亿的数字是否为素数
【发布时间】:2011-05-31 12:21:29
【问题描述】:

我目前在 python 中检查数字素数的算法对于 1000 万到 10 亿之间的数字会变慢。我希望它得到改进,因为我知道我永远不会得到大于 10 亿的数字。

上下文是我无法获得足够快的实现来解决项目 Euler 的问题 60:我在 75 秒内得到问题的答案,而我需要在 60 秒内得到答案。 http://projecteuler.net/index.php?section=problems&id=60

我可以使用的内存很少,所以我无法存储所有低于 10 亿的素数。

我目前正在使用调整为 6k±1 的标准试用分区。还有什么比这更好的吗?对于这么大的数字,我是否已经需要使用 Rabin-Miller 方法。

primes_under_100 = [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]
def isprime(n):
    if n <= 100:
        return n in primes_under_100
    if n % 2 == 0 or n % 3 == 0:
        return False

    for f in range(5, int(n ** .5), 6):
        if n % f == 0 or n % (f + 2) == 0:
            return False
    return True

如何改进这个算法?

精度:我是 python 新手,只想使用 python 3+。


最终代码

对于有兴趣的朋友,使用MAK的思路,我生成了如下代码,大约快了1/3,让我在不到60秒的时间内就得到了欧拉问题的结果!

from bisect import bisect_left
# sqrt(1000000000) = 31622
__primes = sieve(31622)
def is_prime(n):
    # if prime is already in the list, just pick it
    if n <= 31622:
        i = bisect_left(__primes, n)
        return i != len(__primes) and __primes[i] == n
    # Divide by each known prime
    limit = int(n ** .5)
    for p in __primes:
        if p > limit: return True
        if n % p == 0: return False
    # fall back on trial division if n > 1 billion
    for f in range(31627, limit, 6): # 31627 is the next prime
        if n % f == 0 or n % (f + 4) == 0:
            return False
    return True

【问题讨论】:

  • 我知道它的名称是 Python 3 或 Python 3.1,但看起来 Py3k 引用了这些版本。
  • 不应该是ff+4...你能确认一下吗?为什么4
  • 更清楚的是,我使用基数 7 + 6k 而不是 5 + 6k 所以我需要使用 +0、+4、+6、+10 等而不是 +0、+2, +6,+8。优点是我不测试 31625。
  • 警告:纯 python 示例(他的第一个 sn-p)不适用于所有素数。行for f in range(5, int(n ** .5), 6): 应该是for f in range(5, int(n ** .5) + 1, 6):;因为它在显示该数字可被自身的平方根整除之前(太早)退出。
  • @ogregoire:我不知道 Dan​​iel 是否真的对你投了反对票,但我确实发现他的警告很有用,我几乎使用了第一个 sn-p,因为我只需要一个快速而肮脏的 isprime 函数和second 在 Python 2.x 上对我来说开箱即用 ;)

标签: python python-3.x primes


【解决方案1】:

您可以先将您的n 除以您的primes_under_100

另外,预计算更多的素数。

此外,您实际上将 range() 结果存储在内存中 - 请改用 irange() 并使用此内存来运行 Sieve of Eratosthenes algorithm

【讨论】:

  • 好吧,我的内存不是那么短;)而且我使用的是 python 3。我从未在 python 3 中看到过 xrange。
  • xrange 在 py3k 中变成了简单的范围
【解决方案2】:

对于大到 10^9 的数字,一种方法是生成直到 sqrt(10^9) 的所有素数,然后简单地检查输入数字与该列表中数字的可分性。如果一个数字不能被任何其他小于或等于其平方根的素数整除,则它本身必须是素数(它必须至少有一个因数 = sqrt 才能不是素数)。请注意,您不需要测试所有数字的整除性,只需到平方根(大约 32,000 - 我认为非常易于管理)。您可以使用sieve 生成素数列表。

您也可以选择probabilistic prime test。但它们可能更难理解,对于这个问题,只需使用生成的素数列表就足够了。

【讨论】:

  • 是的,我可以存储 32k 个数字。好主意。
  • @Fror,如果数字小于32k,使用二分查找。使用bisect 模块。
【解决方案3】:

为了解决 Project Euler 问题,我按照您在问题中的建议进行了操作:实施 Miller Rabin 测试(在 C# 中,但我怀疑它在 Python 中也会很快)。算法并不难。对于低于 4,759,123,141 的数字,只需检查一个数字是否是基数为 2、7、61 的强伪素数。结合小素数的试除法。

我不知道到目前为止你解决了多少问题,但是有一个快速的素性测试可供你使用,对于很多问题来说都是很有价值的。

【讨论】:

  • 好的,在这种情况下,你怎么称呼小素数?我应该设置什么限制?
  • @Frór:您必须进行实验才能找到最佳值,但我会先尝试所有低于 100 左右的素数。 IIRC 甚至可能是我最终跳过了除基数(在本例中为 2、7、61)之外的所有值的试用除法。
  • 嗯,4,759,123,141 非常少... 可以立即通过偶数除以直到 sqrt 来检查。但是感谢@Pi 的链接-我仍然不明白为什么没有np.miller_rabin 功能(或者scipy,如果这太科学了)。
【解决方案4】:

好吧,我在(非常好)Peter Van Der Heijden 的回答下对我的评论进行了跟进,即“流行的”Python 库中的真正大素数(一般数字)没有什么好处。原来我错了——sympy 中有一个(符号代数的优秀库等):

https://docs.sympy.org/latest/modules/ntheory.html#sympy.ntheory.primetest.isprime

当然,它可能会产生高于10**16 的误报,但这已经比我什么都不做的任何事情要好得多(除了pip install sympy ;))

【讨论】:

  • SymPy 1.1(2017 年 7 月)切换到 BPSW,因此任何 64 位输入都没有误报。在某些情况下,它将使用确定性 Miller-Rabin,但它们也已被验证到 2^64。对于 64 位输入,比这“更好”的唯一方法是优化预测试以使其更快。对于更大的输入,对于大多数目的来说,做更多的事情并没有令人信服的好处(长时间的讨论)。
  • 感谢您的更新!我首先阅读了 sympy 0.x 的旧资源,然后链接到最新的文档。这并没有改变我的观点,即 sympy 很棒,只是比我想象的要好;)
  • 确实,对于大多数情况,我认为这是正确的答案。 OP 正在做 Project Euler,所以这可能不适合早期的问题,但可能适合后来的问题,当然还有其他任何实际用途。
【解决方案5】:
def isprime(num):
if (num==3)or(num==2):
    return(True)
elif (num%2 == 0)or(num%5 == 0):
    return (False)
elif ((((num+1)%6 ==0) or ((num-1)%6 ==0)) and (num>1)):
    return (True)
else:
    return (False)

我认为这段代码是最快的..

【讨论】:

  • 每个素数(除了2和3)都可以用6n (+/-) 1的形式表示
  • 在我的机器中在 0.42194461822509766 秒内检查所有素数,直到 1000000。没有循环,函数体中没有迭代
  • 是的,但反之则不然——每个可以表示为 6n+-1 的数字不一定是素数。例如 25 不是素数 (6*4+1)。
  • 感谢您发现错误!原谅我浪费你的时间。我将修改代码。但是我是一个自学成才的业余程序员
  • 你不能通过检查被 5 整除来解决它。下一个不是素数的是 6*8+1 = 49 = 7*7。抱歉,但这不是真正有效的方法。
猜你喜欢
  • 2015-01-22
  • 1970-01-01
  • 2014-06-05
  • 1970-01-01
  • 2021-01-28
  • 2011-06-09
  • 1970-01-01
  • 2013-04-06
  • 2016-09-27
相关资源
最近更新 更多