【问题标题】:Checking divisibility of numbers检查数字的可分性
【发布时间】:2014-03-18 13:34:00
【问题描述】:

这来自math.se post 这里。我正在检查 2^(n-1)+3 是否可以被 n 整除。这是我写的代码,

def ck(n):
    c=pow(2,n-1,n)+3
    return not c%n

for i in range(10**7,2*10**7):
    if ck(i):
        print(i)
        break

print('Search Complete')

函数ck首先用内置的pow计算2^(n-1)%n,加3,最后得到余数。从数学上讲,这与(2^(n-1)+3)%n 相同,但速度要快得多,因为计算pow(a,b,c)pow(a,b)%c

我想知道我是否可以进行其他优化(在函数中或在 for 循环中)?

range(10**7,2*10**7) 中的值只是我逐步增加的虚拟值,以便搜索不会失控。

[在有人理解错误之前,我完全没有破解哈希]

【问题讨论】:

  • 我建议这样做,xrange 而不是 range 这么大的范围
  • 如果您只使用整数,您可以使用简单的位移来实现平方,而不是调用 pow 函数。
  • @sshashank124 python 3. range 返回一个迭代器。
  • @TimCastelijns 实际上 pow(2,123456789,1000)(2<<123456788)%1000
  • @Sabyasachi,啊,我明白了。我错过了 python-3.x 标签。那么您不能执行以下操作:从i=10**7 开始,然后在每个循环结束时使用while 循环执行i *= 2,当您将参数传递给ck 时,只需执行ck(i+3)。跨度>

标签: python optimization python-3.x modulus


【解决方案1】:

我对@ch3ka 的回答尝试了几种不同的修改,这是我找到的最快的版本。

我维护gmpy2,所以我用它来进行数值计算。 gmpy2 使用 GMP 多精度库,并且通常比使用 Python 的本机整数类型更快。使用gmpy2.powmod(...)pow(...) 快​​得多。

从原始问题的链接,需要gcd(i,30) == 1。所以接下来我尝试使用gmpy2.gcd(...) 来消除 i 不可能的值。这将运行时间大致减少了一半。

然后我通过七次通过范围消除了对gmpy2.gcd(...) 的调用。这将运行时间再次缩短了大约一半。最后,我使用concurrent.futures 将测试分布在 4 个内核上。

这是最终版本:

import sys
import time
from gmpy2 import powmod
from concurrent import futures

BLOCKSIZE = 10**8

def blocktest(block):
    start = max(10, block * BLOCKSIZE)
    end = (block + 1) * BLOCKSIZE

    now = time.time()
    result = []
    result.extend(i for i in range(30*(start//30) + 1, end, 30) if powmod(2, i-1, i) == i-3)
    result.extend(i for i in range(30*(start//30) + 7, end, 30) if powmod(2, i-1, i) == i-3)
    result.extend(i for i in range(30*(start//30) + 11, end, 30) if powmod(2, i-1, i) == i-3)
    result.extend(i for i in range(30*(start//30) + 13, end, 30) if powmod(2, i-1, i) == i-3)
    result.extend(i for i in range(30*(start//30) + 17, end, 30) if powmod(2, i-1, i) == i-3)
    result.extend(i for i in range(30*(start//30) + 19, end, 30) if powmod(2, i-1, i) == i-3)
    result.extend(i for i in range(30*(start//30) + 23, end, 30) if powmod(2, i-1, i) == i-3)
    result.extend(i for i in range(30*(start//30) + 29, end, 30) if powmod(2, i-1, i) == i-3)

    return (start, end - 1, time.time() - now, result)

if __name__ == "__main__":
    print("starting time: ", time.strftime("%H:%M:%S"))
    with futures.ProcessPoolExecutor(max_workers=4) as executor:
        for s, e, t, r in executor.map(blocktest, range(10)):
            print("range({:,}, {:,}) time: {} et: {:6.2f} {!r}".format(s, e, time.strftime("%H:%M:%S"), t, r))

测试到 10**9 大约需要 1 分 15 秒。仅用了 16 分钟多一点就找到了第一个成功的值:13957196317。

【讨论】:

  • 那么 13957196317 是第一个成功的值吗?伟大的。 :D
  • @Sabyasachi 没有其他解决方案低于 300,000,000,000。
  • 哇老兄,慢点。你不必在这方面全神贯注。 :P 我一直在寻找最小的,最小的。 phew 你可以检查post 找到另一个候选对象,这个13957196317 已经在没有暴力破解的情况下被预测,我们不确定它是否是最小的。事实证明,数学毕竟比蛮力好。
  • @Sabyasachi 我最感兴趣的是在几个不同的 CPU 架构上测试多核扩展。我只是让它在服务器上运行一夜。
【解决方案2】:

这个版本稍微快了一点,因为我们在这里放弃了函数调用开销:

print(next(i for i in range(lowerbound,upperbound) if not (pow(2,i-1,i)+3)%i), 'Search Complete')

通过我的快速和肮脏的测量,这将提高约 10% 的速度:

python /tmp/so1.py  46.54s user 0.00s system 99% cpu 46.558 total

python /tmp/so2.py  52.50s user 0.01s system 99% cpu 52.530 total

如果不将 % 的结果转换为 bool,我也尝试过,但使用 0 测试对象身份会更快 - 但事实并非如此。

【讨论】:

  • 好吧,总是有选择 C ​​的选择...但是使用普通的 python,我怀疑你可以做得更快(并且仍然蛮力它)。
  • 是的。也没什么好找的。由于我正在寻找最小的 n,并且通过纯粹的数论方法,我们已经有了一个大约 1.3x10^11 的解决方案
  • 所以我只需要耗尽它。在 2^31 - 1 之前也没有解决方案
  • 使用gmpy2可以提高性能。 gmpy2 提供对非常快速的 GMP 库的访问。通过将pow(...) 替换为gmpy2.powmod(...),我将性能提高了50%。
猜你喜欢
  • 1970-01-01
  • 2022-07-21
  • 2016-04-05
  • 1970-01-01
  • 2017-07-11
  • 1970-01-01
  • 1970-01-01
  • 2019-06-28
  • 2020-11-03
相关资源
最近更新 更多