在Python中查找数字的所有因子的最有效方法是什么?

Adn*_*nan 130 python algorithm performance factorization python-2.7

有人可以向我解释一种在Python(2.7)中查找数字的所有因子的有效方法吗?

我可以创建算法来完成这项工作,但我认为编码很差,并且执行大量数据的结果需要很长时间.

agf*_*agf 249

from functools import reduce

def factors(n):    
    return set(reduce(list.__add__, 
                ([i, n//i] for i in range(1, int(n**0.5) + 1) if n % i == 0)))
Run Code Online (Sandbox Code Playgroud)

这将很快返回所有因素n.

为什么平方根为上限?

sqrt(x) * sqrt(x) = x.因此,如果两个因素相同,它们都是平方根.如果你将一个因子做大,你必须使另一个因子变小.这意味着两者中的一个总是小于或等于sqrt(x),因此您只需要搜索到该点以找到两个匹配因子中的一个.然后你可以x / fac1用来获取fac2.

reduce(list.__add__, ...)走的小名单[fac1, fac2],并在一个长长的清单一起加入他们.

[i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0返回两个因素,如果当你除以其余n由较小的一个是零(它并不需要检查较大的一个过;它只是获取除以n通过较小的一个)

set(...)在外面摆脱重复,这仅发生于完美的正方形.因为n = 4,这将返回2两次,所以set摆脱其中一个.

  • 我知道这是一个老问题,但在Python 3.x中你需要添加`from functools import reduce`来使这个工作. (6认同)
  • 如果我使用`if not n%i`而不是`if n%i == 0`,似乎执行速度提高15% (5认同)
  • @unseen_rider:听起来不错.你能提供任何支持吗? (4认同)
  • @sthzg我们希望它返回一个整数,而不是一个浮点数,并且在Python 3上```将返回一个浮点数,即使两个参数都是整数且它们是完全可分的,即`4/2 == 2.0`不是`2` . (3认同)

Ste*_*ima 50

@agf提供的解决方案很棒,但通过检查奇偶校验,可以使任意奇数的运行时间缩短约50%.由于奇数的因子本身总是奇数,所以在处理奇数时没有必要检查它们.

我刚刚开始自己解决Project Euler的难题.在某些问题中,在两个嵌套for循环内部调用除数检查,因此该函数的性能至关重要.

将这一事实与agf优秀的解决方案相结合,我最终得到了这个功能:

from math import sqrt
def factors(n):
        step = 2 if n%2 else 1
        return set(reduce(list.__add__,
                    ([i, n//i] for i in range(1, int(sqrt(n))+1, step) if n % i == 0)))
Run Code Online (Sandbox Code Playgroud)

但是,对于较小的数字(〜<100),此更改的额外开销可能会导致函数花费更长时间.

我跑了一些测试来检查速度.以下是使用的代码.为了产生不同的图,我相应地改变了X = range(1,100,1).

import timeit
from math import sqrt
from matplotlib.pyplot import plot, legend, show

def factors_1(n):
    step = 2 if n%2 else 1
    return set(reduce(list.__add__,
                ([i, n//i] for i in range(1, int(sqrt(n))+1, step) if n % i == 0)))

def factors_2(n):
    return set(reduce(list.__add__,
                ([i, n//i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0)))

X = range(1,100000,1000)
Y = []
for i in X:
    f_1 = timeit.timeit('factors_1({})'.format(i), setup='from __main__ import factors_1', number=10000)
    f_2 = timeit.timeit('factors_2({})'.format(i), setup='from __main__ import factors_2', number=10000)
    Y.append(f_1/f_2)
plot(X,Y, label='Running time with/without parity check')
legend()
show()
Run Code Online (Sandbox Code Playgroud)

X =范围(1,100,1) X =范围(1,100,1)

这里没有显着差异,但数字越大,优势显而易见:

X =范围(1,100000,1000)(仅奇数) X =范围(1,100000,1000)(仅奇数)

X =范围(2,100000,100)(仅偶数) X =范围(2,100000,100)(仅偶数)

X =范围(1,100000,1001)(交替奇偶校验) X =范围(1,100000,1001)(交替奇偶校验)


ste*_*eha 28

agf的答案非常酷.我想看看是否可以重写它以避免使用reduce().这就是我想出的:

import itertools
flatten_iter = itertools.chain.from_iterable
def factors(n):
    return set(flatten_iter((i, n//i) 
                for i in range(1, int(n**0.5)+1) if n % i == 0))
Run Code Online (Sandbox Code Playgroud)

我还尝试了一个使用棘手的生成器函数的版本:

def factors(n):
    return set(x for tup in ([i, n//i] 
                for i in range(1, int(n**0.5)+1) if n % i == 0) for x in tup)
Run Code Online (Sandbox Code Playgroud)

我通过计算计时:

start = 10000000
end = start + 40000
for n in range(start, end):
    factors(n)
Run Code Online (Sandbox Code Playgroud)

我跑了一次让Python编译它,然后在time(1)命令下运行三次并保持最佳时间.

  • 减少版本:11.58秒
  • itertools版本:11.49秒
  • 棘手的版本:​​11.12秒

请注意,itertools版本正在构建一个元组并将其传递给flatten_iter().如果我更改代码来构建列表,它会稍微减慢:

  • iterools(列表)版本:11.62秒

我相信棘手的生成器函数版本在Python中是最快的.但它并不比降低版本快得多,根据我的测量值大约快4%.

  • 您可以简化“棘手的版本”(删除不必要的“ tup in”):`factors = lambda n:{f for i in range(1,int(n ** 0.5)+1)如果n%i == 0对于[i,n // i]}中的f (2认同)

Ery*_*Sun 10

agf答案的另一种方法:

def factors(n):    
    result = set()
    for i in range(1, int(n ** 0.5) + 1):
        div, mod = divmod(n, i)
        if mod == 0:
            result |= {i, div}
    return result
Run Code Online (Sandbox Code Playgroud)

  • divmod(x,y)返回((xx%y)/ y,x%y),即除法的商和余数. (3认同)
  • 你能解释一下div,mod部分吗? (2认同)

Goo*_*JaY 9

求一个数的因数的最简单方法:

def factors(x):
    return [i for i in range(1,x+1) if x%i==0]
Run Code Online (Sandbox Code Playgroud)


Jul*_*ian 7

这是@ agf解决方案的替代方案,它以更加pythonic的方式实现相同的算法:

def factors(n):
    return set(
        factor for i in range(1, int(n**0.5) + 1) if n % i == 0
        for factor in (i, n//i)
    )
Run Code Online (Sandbox Code Playgroud)

此解决方案适用于Python 2和Python 3,没有导入,并且更具可读性.我没有测试过这种方法的性能,但渐渐地它应该是相同的,如果性能是一个严重的问题,那么这两种解决方案都不是最佳的.


Pra*_*tal 6

进一步改进afg&eryksun的解决方案.以下代码返回所有因子的排序列表,而不会更改运行时渐近复杂度:

    def factors(n):    
        l1, l2 = [], []
        for i in range(1, int(n ** 0.5) + 1):
            q,r = n//i, n%i     # Alter: divmod() fn can be used.
            if r == 0:
                l1.append(i) 
                l2.append(q)    # q's obtained are decreasing.
        if l1[-1] == l2[-1]:    # To avoid duplication of the possible factor sqrt(n)
            l1.pop()
        l2.reverse()
        return l1 + l2
Run Code Online (Sandbox Code Playgroud)

想法:而不是使用list.sort()函数来获得一个排序列表,它给出了nlog(n)的复杂性; 在l2上使用list.reverse()要快得多,这会带来O(n)的复杂性.(这就是python的制作方法.)在l2.reverse()之后,l2可以附加到l1以获得排序的因子列表.

注意,l1包含正在增加的i- s.l2包含正在减少的q- s.这就是使用上述想法背后的原因.


小智 6

我已经尝试了大部分这些精彩的答案与时间比较他们的效率与我的简单功能,但我不断看到我的表现超过了这里列出的那些.我想我会分享它,看看你们都在想什么.

def factors(n):
    results = set()
    for i in xrange(1, int(math.sqrt(n)) + 1):
        if n % i == 0:
            results.add(i)
            results.add(int(n/i))
    return results
Run Code Online (Sandbox Code Playgroud)

正如它所写的那样,你必须导入数学来测试,但用n**替换math.sqrt(n).5应该也能正常工作.我不打扰浪费时间检查重复项,因为重复项不能存在于集合中.

  • @TristanForward:那不是在Python中for循环的工作方式。xrange(1,int(math.sqrt(n))+ 1)被评估一次。 (2认同)

Evg*_*eev 6

SymPy中有一种称为强度因子的行业优势算法:

>>> from sympy import factorint
>>> factorint(2**70 + 3**80) 
{5: 2,
 41: 1,
 101: 1,
 181: 1,
 821: 1,
 1597: 1,
 5393: 1,
 27188665321L: 1,
 41030818561L: 1}
Run Code Online (Sandbox Code Playgroud)

这花了不到一分钟的时间。它在多种方法之间切换。请参阅上面链接的文档。

考虑到所有主要因素,可以轻松构建所有其他因素。


请注意,即使允许接受的答案运行足够长的时间(即一个永恒的时间)来分解上述数字,但对于某些较大的数字,它将失败,例如以下示例。这是由于马虎int(n**0.5)。例如,当时n = 10000000000000079**2,我们有

>>> int(n**0.5)
10000000000000078L
Run Code Online (Sandbox Code Playgroud)

由于10000000000000079是质数,因此可接受的答案的算法将永远找不到此因子。请注意,它不只是一对一的。对于更大的数字,它将更多。因此,最好避免在这类算法中使用浮点数。

  • 它找不到所有除数,而仅找到主要因数,因此它并不是真正的答案。您应该展示如何构建所有其他因素,而不仅仅是说这很简单!顺便说一句,sympy.divisors可能是回答这个问题的更好选择。 (2认同)
  • @ColinPitrat:已检查。正如预期的那样,可接受的答案与“ sympy.divisors”的速度大致相同,即为100,000,而对于更高的速度则较慢(当速度实际上很重要时)。(当然,“ sympy.divisors”可用于类似“ 10000000000000079 ** 2”的数字。) (2认同)

Bru*_*ino 6

对于n高达10**16(甚至可能更多),这是一个快速纯Python 3.6解决方案,

from itertools import compress

def primes(n):
    """ Returns  a list of primes < n for n > 2 """
    sieve = bytearray([True]) * (n//2)
    for i in range(3,int(n**0.5)+1,2):
        if sieve[i//2]:
            sieve[i*i//2::i] = bytearray((n-i*i-1)//(2*i)+1)
    return [2,*compress(range(3,n,2), sieve[1:])]

def factorization(n):
    """ Returns a list of the prime factorization of n """
    pf = []
    for p in primeslist:
      if p*p > n : break
      count = 0
      while not n % p:
        n //= p
        count += 1
      if count > 0: pf.append((p, count))
    if n > 1: pf.append((n, 1))
    return pf

def divisors(n):
    """ Returns an unsorted list of the divisors of n """
    divs = [1]
    for p, e in factorization(n):
        divs += [x*p**k for k in range(1,e+1) for x in divs]
    return divs

n = 600851475143
primeslist = primes(int(n**0.5)+1) 
print(divisors(n))
Run Code Online (Sandbox Code Playgroud)


dan*_*lmo 5

这是另一种没有减少的替代方案,对于大数字表现良好.它用于sum展平列表.

def factors(n):
    return set(sum([[i, n//i] for i in xrange(1, int(n**0.5)+1) if not n%i], []))
Run Code Online (Sandbox Code Playgroud)

  • 这不是,它是不必要的二次时间。不要使用“sum”或“reduce(list.__add__)”来展平列表。 (2认同)

Mar*_*eir 5

我最近开发了一种新的整数分解方法,称为平滑子和搜索(SSS)。这是我在 Python 中的实现: https: //github.com/sbaresearch/smoothsubsumsearch

它可以在约 0.2 秒内分解 30 位数字,在约 2 秒内分解 40 位数字,在约 30 秒内分解 50 位数字,在约 200 秒内分解 60 位数字,在约 3000 秒内分解 70 位数字。与 sympy 中的自初始化二次筛实现(我能找到的 Python 中最高效的二次筛实现)相比,它的运行速度大约快 5 到 7 倍。SSS 的详细描述见:https ://arxiv.org/abs/2301.10529