【问题标题】:Faster Way To Find Factors of n only using the math library仅使用数学库查找 n 因子的更快方法
【发布时间】:2020-07-03 21:27:41
【问题描述】:

在将其标记为重复之前...

我需要找到 n 的所有因数(有很多解决方案)。我能够实现的最快的是通过循环 2 到 n 的 sqrt 范围。这就是我迄今为止所拥有的......

def get_factors(n):
    factors = set()
    for i in range(2, int(math.sqrt(n) + 1)):
        if n % i == 0:
            factors.update([i, n // i])
    return factors

这是查找n 的因子的一种非常快速的方法,但我想知道是否有更快的方法来查找n 的因子。我唯一的限制是我只能在 Python 3.7 中使用数学库。关于如何更快地完成此操作的任何想法?我找不到只使用数学库的答案。我可以做些什么来提高我当前算法的效率吗?

【问题讨论】:

  • 数字的大小是多少?
  • 18 位不是很大。这给了你以 262143 为界的整数
  • 对不起。我犯了一个错误。我的意思是 64 位。

标签: python performance math python-3.7 factorization


【解决方案1】:

编辑:就像@John Coleman 在此解决方案的评论中所说,最好在计算素数时获取因子,这样可以避免额外的工作,以防您完成因式分解筛完之前的数字。更新后的代码是:

def factors(number):
    n=int(number**.5)+1
    x=number
    divisors=[]
    era =[1] * n
    primes=[]
    for p in range(2, n):
        if era[p]:
            primes.append(p)
            while x%p==0:
                x//=p
                divisors.append(p)
            for i in range(p*p, n, p):
                era[i] = False
    if x!=1:
        divisors.append(x)    
    return divisors

OG解决方案:

使用Erathostenes Sieve 获取 2 和 sqrt(n) 之间的质因数,然后检查哪些是 n 的除数。这将大大减少代码的运行时间。

Erathostenes 筛子只使用列表、操作%,>=,<= 和布尔值。

这是一个比我分享给你的链接中的更短的实现:

def factors(number):
    n=int(number**.5)+1
    era =[1] * n
    primes=[]
    for p in range(2, n):
        if era[p]:
            primes.append(p)
            for i in range(p*p, n, p):
                era[i] = False
    divisors=[]
    x=number
    for i in primes:
        while x%i==0:
            x//=i
            divisors.append(i)
    if x!=1:
        divisors.append(x)    
    return divisors

【讨论】:

  • 查找平方根以下的所有素数似乎效率低下。一旦发现素数是除数,立即使用它来减少问题。比如n = 2**40,你的算法首先会找到大约 80,000 个素数,尽管它只需要 1 个。
  • 你是对的,这是一个更好的解决方案。让我为将来可能找到此解决方案的用户编辑此内容。
  • @HasnainAli--1) 因素不会产生正确的答案。对于 n = 32768,结果为 [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] 而不是 {16384, 8192, 2, 4096, 4, 2048, 32, 1024, 8, 64, 128, 256, 16, 512}。 2)此外,对于 n = 32、768(使用 timeit),它实际上慢了 2 倍。我认为目标是产生相同输出的更快算法还是我错过了什么?
  • @DarrylG 好吧,目标是找到乘以 n 的数字(因子)。 2^15 是质因数分解。这些数字中的每一个(我可能应该对我的最后一组进行排序)都可以除以 n。
  • 对不起,我将添加除数的部分。您将通过此解决方案获得主要因素,并且从中也可以获得除数,但代码无法获得它们。
【解决方案2】:

求一个数的所有因数的最快方法

约束——不要使用数学以外的任何外部库

测试了 4 种方法

  1. 审判部门(由提问者@HasnainAli 发布代码)又名审判
  2. Eratosthenes 筛子(来自@MonsieurGalois 帖子)又名筛子
  3. 素数分解Inspired by又名分解
  4. 基于受 Wheel Factorization aka Wheel 启发的 Wheel Factorization 的素数

结果

结果是相对于审判部门的,即(审判部门时间)÷(其他接近时间)

@Davakar 使用 Benchit 的基准,它使用 timeit

N            trial  sieve     prime_fac  wheel_fac                                           
1             1.0  1.070048   1.129752   1.104619
2             1.0  1.438679   3.201589   1.119284
4             1.0  1.492564   2.749838   1.176149
8             1.0  1.595604   3.164555   1.290554
16            1.0  1.707575   2.917286   1.172851
32            1.0  2.051244   3.070331   1.262998
64            1.0  1.982820   2.701439   1.073524
128           1.0  2.188541   2.776955   1.098292
256           1.0  2.086762   2.442863   0.945812
512           1.0  2.365761   2.446502   0.914936
1024          1.0  2.516539   2.076006   0.777048
2048          1.0  2.518634   1.878156   0.690900
4096          1.0  2.546800   1.585665   0.573352
8192          1.0  2.623528   1.351017   0.484521
16384         1.0  2.642640   1.117743   0.395437
32768         1.0  2.796339   0.920231   0.327264
65536         1.0  2.757787   0.725866   0.258145
131072        1.0  2.790135   0.529174   0.189576
262144        1.0  2.676348   0.374986   0.148726
524288        1.0  2.877928   0.269510   0.107237
1048576       1.0  2.522501   0.189929   0.080233
2097152       1.0  3.142147   0.125797   0.053157
4194304       1.0  2.673095   0.105293   0.045798
8388608       1.0  2.675686   0.075033   0.030105
16777216      1.0  2.508037   0.057209   0.022760
33554432      1.0  2.491193   0.038634   0.015440
67108864      1.0  2.485025   0.029142   0.011826
134217728     1.0  2.493403   0.021297   0.008597
268435456     1.0  2.492891   0.015538   0.006098
536870912     1.0  2.448088   0.011308   0.004539
1073741824    1.0  1.512157   0.005103   0.002075

结论:

  • 筛法总是比试除法慢(即比率列 > 1)
  • 试除法最快可达 n ~256
  • 车轮分解方法总体速度最快(即 n = 2**30 的 481X 试验除法,即 1/0.002075 ~ 481)

代码

方法一:原帖

import math

def trial(n):
  " Factors by trial division "
  factors = set()
  for i in range(2, int(math.sqrt(n) + 1)):
      if n % i == 0:
          factors.update([i, n // i])
  return factors

方法 2--筛(@MonsieurGalois 帖子)

def factors_sieve(number):
  " Using primes in trial division "

  # Find primes up to sqrt(n)
  n=int(number**.5)+1
  era =[1] * n
  primes=[]
  for p in range(2, n):
      if era[p]:
          primes.append(p)
          for i in range(p*p, n, p):
              era[i] = False

  # Trial division using primes
  divisors=[]
  x=number
  for i in primes:
      while x%i==0:
          x//=i
          divisors.append(i)
  if x!=1:
      divisors.append(x)    
  return divisors

方法3--基于素数分解求除数

Inspired by

def generateDivisors(curIndex, curDivisor, arr): 
  " Yields the next factor based upon prime exponent " 
  if (curIndex == len(arr)): 
    yield curDivisor
    return

  for i in range(arr[curIndex][0] + 1): 
    yield from generateDivisors(curIndex + 1, curDivisor, arr) 
    curDivisor *= arr[curIndex][1]


def prime_factorization(n):
    " Generator for factors of n "

    # To store the prime factors along 
    # with their highest power 
    arr = [] 

    # Finding prime factorization of n 
    i = 2
    while(i * i <= n): 
      if (n % i == 0): 
        count = 0
        while (n % i == 0): 
          n //= i 
          count += 1
        
        # For every prime factor we are storing 
        # count of it's occurenceand itself. 
        arr.append([count, i])

      i += 2 if i % 2 else 1
    
    # If n is prime 
    if (n > 1): 
      arr.append([1, n]) 
    
    curIndex = 0
    curDivisor = 1
    
    # Generate all the divisors 
    yield from generateDivisors(curIndex, curDivisor, arr) 

方法4--车轮分解

def wheel_factorization(n): 
    " Factors of n based upon getting primes for trial division based upon wheel factorization "

    # Init to 1 and number
    result = {1, n}

    # set up prime generator
    primes = prime_generator()   

    # Get next prime
    i = next(primes)

    while(i * i <= n): 
      if (n % i == 0):
        result.add(i)
        
        while (n % i == 0): 
          n //= i 
          result.add(n)

      i = next(primes)  # use next prime

    return result

def prime_generator():
  " Generator for primes using trial division and wheel method "
  yield 2; yield 3; yield 5; yield 7;

  def next_seq(r):
    " next in the equence of primes "
    f = next(r)
    yield f

    r = (n for n in r if n % f)  # Trial division
    yield from next_seq(r)

  def wheel():
    " cycles through numbers in wheel whl "
    whl = [2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 4, 2,
          6, 4, 6, 8, 4, 2, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6,
          2, 6, 6, 4, 2, 4, 6, 2, 6, 4, 2, 4, 2, 10, 2, 10]
    
    while whl:
      for element in whl:
        yield element

  def wheel_accumulate(n, gen):
    " accumulate wheel numbers "
    yield n

    total = n
    for element in gen:
      total += element
      yield total

  for p in next_seq(wheel_accumulate(11, wheel())):
    yield p

测试代码

from timeit import timeit

cnt = 100000  # base number of repeats for timeit

print('{0: >12} {1: >9} {2: >9} {3: >9} {4: >9}'.format('N', 'Trial', 'Primes', 'Division', 'Wheel'))
for k in range(1, 31):
  N = 2**k
  count = cnt // k   # adjust repeats based upon size of k
  x = timeit(lambda:trial(N), number=count)
  y = timeit(lambda:sieve(N), number=count)
  z = timeit(lambda:list(prime_factorization(N)), number=count)
  k = timeit(lambda:list(wheel_factorization(N)), number=count)
  print(f"{N:12d} {1:9d} {x/y:9.5f} {x/z:9.5f} {x/k:9.5f}")

【讨论】:

  • 这些方法都适用于较小的值。对于接近 2**64 上限的值,有更快的方法,如 Pollard-rho、SQUFOF 和确定性 ECM。至少据称它们更快。需要将它们添加到您的测试中才能确定。通常会使用混合方法,例如先试除法或可能会根据您的结果进行轮换,直至达到一定限度,然后切换到更高级的方法。
  • @PresidentJamesK.Polk——感谢您的反馈。你是对的,这些可能仅在较小的值(即~10^12 左右)上优于其他方法。此外,查看这些其他方法的代码,它们还需要额外的库(即 Pollard-rho 需要随机等),这违反了 Poster 仅使用数学库的限制。正确,还是我错过了什么?
  • Pollard-rho 不需要随机。 Pollard-Rho 的分析假设随机性,但实际实现在没有它的情况下工作正常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-18
  • 2018-02-19
  • 2014-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多