[英]fastest way to produce a list of all divisors of a number
给定一个数字n
,我想生成n
的所有唯一除数的排序列表(没有重复)。 解决这个问题真的很简单,但我感兴趣的是效率。 最快的方法是什么?
这是一种方法,使用纯 python:
def get_divisors(n):
"""
:param n: positive integer.
:return: list of all different divisors of n.
"""
if n <= 0:
return []
divisors = [1, n]
for div in range(1, int(n ** 0.5 + 1)):
if n % div == 0:
divisors.extend([n // div, div])
return sorted(list(set(divisors)))
关于如何优化它的任何建议? 欢迎使用 Numpy 和其他优化库。
您已经进行了平方根优化。 接下来是利用 numpy 的并行性:
import numpy as np
def getDivs(N):
divs = np.arange(1,int(N**0.5)+1) # potential divisors up to √N
divs = divs[N % divs==0] # divisors
comp = N//divs[::-1] # complement quotients
return np.concatenate((divs,comp[divs[-1]==comp[0]:])) # combined
print(getDivs(1001**2))
[ 1 7 11 13 49 77 91 121 143
169 539 637 847 1001 1183 1573 1859 5929
7007 8281 11011 13013 20449 77077 91091 143143 1002001]
comp[divs[-1]==comp[0]:]
如果是 integer,则避免重复平方根。
我希望最快的方法是从输入的完整素数分解中“从头开始”构建除数。 但是你不会在这里得到关于“最快的方法”的答案来开始一个素因数分解:这本身就是一个巨大的话题,对于大整数来说仍然是一个非常活跃的研究领域。
下面的方法简单地使用试除法,向上通过剩余因子的平方根,但跳过 2 和 3 的倍数(除了 2 和 3 本身)。 通过 32 位整数,这是相当快的。
给定一个素数分解
n == p0**e0 * p1**e1 * p2**e2 ...
那么n
的除数是所有且仅是该形式的整数,其指数小于或等于e0, e1, e2, ...
。 itertools.product()
可以直接生成所有这样的指数元组,然后只需执行幂和结果相乘即可。
def get_divisors(n):
from math import isqrt, prod
from itertools import accumulate, chain, cycle, product
if n <= 1:
return [n]
ps = []
exps = []
limit = isqrt(n)
for cand in chain([2, 3], accumulate(cycle([2, 4]),
initial=5)):
if cand > limit:
break
if n % cand == 0:
count = 1
n //= cand
while n % cand == 0:
count += 1
n //= cand
ps.append(cand)
exps.append(count)
limit = isqrt(n)
if n > 1:
ps.append(n)
exps.append(1)
result = []
for pows in product(*(range(exp + 1) for exp in exps)):
result.append(prod(p**e for p, e in zip(ps, pows)))
return sorted(result)
>>> for i in range(22):
... print(i, get_divisors(i))
0 [0]
1 [1]
2 [1, 2]
3 [1, 3]
4 [1, 2, 4]
5 [1, 5]
6 [1, 2, 3, 6]
7 [1, 7]
8 [1, 2, 4, 8]
9 [1, 3, 9]
10 [1, 2, 5, 10]
11 [1, 11]
12 [1, 2, 3, 4, 6, 12]
13 [1, 13]
14 [1, 2, 7, 14]
15 [1, 3, 5, 15]
16 [1, 2, 4, 8, 16]
17 [1, 17]
18 [1, 2, 3, 6, 9, 18]
19 [1, 19]
20 [1, 2, 4, 5, 10, 20]
21 [1, 3, 7, 21]
由于假设输入不大于 10 亿,因此您可以使用Wheel 分解(以{2, 3}
为基础)计算主要因子,这是对基本试验除法的改进。 这很快,因为素数因子的数量总是很少(不超过 30 个值)。 然后,您可以将主要因素转换为除数列表(可能有数千个项目)。 使用Numba 即时编译器(JIT) 可以有效地计算分解。 这是生成的代码:
import numba as nb
@nb.njit('List(int_)(int_)')
def get_prime_divisors(n):
divisors = []
while n % 2 == 0:
divisors.append(2)
n //= 2
while n % 3 == 0:
divisors.append(3)
n //= 3
i = 5
while i*i <= n:
for k in (i, i+2):
while n % k == 0:
divisors.append(k)
n //= k
i += 6
if n > 1:
divisors.append(n)
return divisors
@nb.njit('List(int_)(int_)')
def get_divisors(n):
divisors = []
if n == 1:
divisors.append(1)
elif n > 1:
prime_factors = get_prime_divisors(n)
divisors = [1]
last_prime = 0
factor = 0
slice_len = 0
# Find all the products that are divisors of n
for prime in prime_factors:
if last_prime != prime:
slice_len = len(divisors)
factor = prime
else:
factor *= prime
for i in range(slice_len):
divisors.append(divisors[i] * factor)
last_prime = prime
divisors.sort()
return divisors
以下是我机器上 5000 个介于 1 到 100 万之间的随机整数的计时:
Initial get_divisors: 125 ms
Alain's getDivs: 40 ms
Tim Peters' get_divisors: 87 ms
This solution: 7 ms
以下是我机器上 2000 个介于 1 到 10 亿之间的随机整数的计时:
Initial get_divisors: 1403 ms
Alain's getDivs: 231 ms
Tim Peters' get_divisors: 178 ms
This solution: 8 ms
因此,该解决方案比最快的替代解决方案快 6~22 倍,比初始实施快 18~175 倍。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.