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


142

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

我可以创建一个算法来执行此操作,但是我认为它的编码很差,并且花费大量时间才能生成大量结果。


3
我不懂python。但是此页面可能对您有用en.wikipedia.org/wiki/Integer_factorization
Stan

3
使用primefac怎么样?pypi.python.org/pypi/primefac
Zubo,

Answers:


265
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)))

这将很快返回所有因素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摆脱其中之一。


1
从我的计算机上的算法列表我复制粘贴此,我所做的就是封装sqrt-它可能从之前人们真正想支持Python 3下,我认为我得到了它从网站上试了一下反对__iadd__,这是更快。我似乎还记得一些关于x**0.5sqrt(x)于某个时刻的信息-这样更加安全。
2011年

7
如果我if not n % i改为使用,似乎执行速度会提高15%if n % i == 0
dansalmo

3
@sthzg我们希望它返回一个整数,而不是浮点数,并且在Python 3上,/即使两个参数都是整数并且它们都是可整的,即4 / 2 == 2.0不是,也将返回浮点数2
2015年

7
我知道这是一个老问题,但是在Python 3.x中,您需要添加from functools import reduce才能使它起作用。
anonymoose

5
@unseen_rider:听起来不对。您能提供任何支持吗?
Ry-

54

@agf提出的解决方案很棒,但是通过检查奇偶校验,可以将任意奇数的运行时间缩短约50%。由于奇数的因子本身始终都是奇数,因此在处理奇数时不必检查它们。

我刚刚开始解决欧拉计划困惑了自己。在某些问题中,在两个嵌套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)))

但是,对于较小的数字(〜<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()

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

这里没有显着差异,但是数量更大时,优势显而易见:

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

X = range(2,100000,100)(仅偶数) X = range(2,100000,100)(仅偶数)

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


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))

我还尝试了使用棘手的生成器功能的版本:

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)

我通过计算来计时:

start = 10000000
end = start + 40000
for n in range(start, end):
    factors(n)

我运行了一次以让Python对其进行编译,然后在time(1)命令下运行了三次,并保持了最佳时间。

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

注意itertools版本正在构建一个元组,并将其传递给flatten_iter()。如果我更改代码以构建列表,则它会稍微降低速度:

  • iterools(列表)版本:11.62秒

我相信棘手的生成器函数版本是Python中最快的。但这并没有比简化版本快很多,根据我的测量,速度大约快4%。


2
您可以简化“棘手的版本”(删除不必要的内容for tup in):factors = lambda n: {f for i in range(1, int(n**0.5)+1) if n % i == 0 for f in [i, n//i]}
jfs

11

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

1
您能解释一下div,mod部分吗?
阿德南

3
divmod(x,y)返回((xx%y)/ y,x%y),即除法的商和余数。
c4757p 2011年

这不能很好地处理重复因素-例如尝试81。
phkahler 2011年

您的答案更加清楚,因此我能够充分理解它以至于误解了。我在考虑素数分解,您想在其中调用多个3。这应该很好,因为这就是OP所要求的。
phkahler 2011年

我将所有内容都排成一行,因为agf的回答是这样。我很想知道是否reduce()速度要快得多,所以除了reduce()agf 以外,我几乎做了其他所有工作。为了提高可读性,很高兴看到这样的函数调用,is_even(n)而不是像这样的表达式n % 2 == 0
steveha 2013年

9

这是@agf解决方案的替代方法,该解决方案以更Python的样式实现相同的算法:

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)
    )

此解决方案在没有导入的Python 2和Python 3中均可使用,并且可读性更高。我还没有测试这种方法的性能,但是渐近地它应该是相同的,并且如果性能是一个严重的问题,那么这两种解决方案都不是最优的。


7

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}

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

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


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

>>> int(n**0.5)
10000000000000078L

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


2
它找不到所有除数,而仅找到素因数,因此它并不是真正的答案。您应该展示如何构建所有其他因素,而不仅仅是说这很简单!顺便说一句,sympy.divisors可能是回答这个问题的更好选择。
Colin Pitrat

并请注意sympy.divisors并不比接受的解决方案快很多。
Colin Pitrat

@ColinPitrat:我有点怀疑这sympy.divisors是否快得多,尤其是对于除数很少的数字。有基准吗?
Ry-

@Ry一年前写此评论时,我做了一个。撰写一篇文章需要2分钟,因此请仔细检查。
Colin Pitrat,

3
@ColinPitrat:已检查。正如预期的那样,可接受的答案与sympy.divisors100,000的速度大致相同,而对于更高的速度则较慢(当速度实际上很重要时)。(当然,也sympy.divisors可以处理类似的数字10000000000000079**2。)
Ry-

7

对于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))

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

想法:不要使用list.sort()函数来获取有序列表,从而使nlog(n)变得复杂。在l2上使用list.reverse()更快,这会增加O(n)的复杂度。(这就是python的制作方法。)在l2.reverse()之后,可以将l2附加到l1以获得因子的排序列表。

注意,l1包含不断增加的i -s。l2包含q -s递减。这就是使用上述想法的原因。


可以肯定的list.reverse是O(n)不是O(1),不是可以改变整体复杂度。
2013年

是的,这是对的。我犯了一个错误。应该是O(n)。(我现在将答案更新为正确的答案)
Pranjal Mittal

它比@ steveha或@agf的解决方案慢大约2倍。
jfs

您可以通过返回l1 + l2.reversed()而不是反转列表来获得小幅(2-3%)的速度改进。
Rakurai

6

我用timeit尝试了这些绝妙的答案中的大多数,以比较它们的效率与我的简单功能,但我不断看到我的表现优于此处列出的那些。我想分享一下,看看大家都怎么想。

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

在编写本文时,您必须导入数学以进行测试,但是用n **。5替换math.sqrt(n)也应同样有效。我不会浪费时间检查重复项,因为无论如何重复项都不能存在于集合中。


好东西!如果将int(math.sqrt(n))+ 1置于for循环之外,您应该会从中获得更多性能,因为不必每次for循环迭代都重新计算它
Tristan Forward

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

5

这是另一个没有reduce的替代方法,可以很好地处理大量数据。它用于sum拉平列表。

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

1
并非如此,而是不必要的二次时间。不要使用sumreduce(list.__add__)拼合列表。
juanpa.arrivillaga '18

4

确保抓取的数字大于sqrt(number_to_factor)不寻常数字(例如99,其具有3 * 3 * 11和)floor sqrt(99)+1 == 10

import math

def factor(x):
  if x == 0 or x == 1:
    return None
  res = []
  for i in range(2,int(math.floor(math.sqrt(x)+1))):
    while x % i == 0:
      x /= i
      res.append(i)
  if x != 1: # Unusual numbers
    res.append(x)
  return res

1
它不会产生所有数量的因素。它计算出一个数的质因数,例如,对于x=8预期:[1, 2, 4, 8],得到:[2, 2, 2]
jfs

@agf给定的代码中包含9时发现11。'i = 9-> 99%9 == 0-> 9并添加99/9 = 11。
Steinar Lima

4

查找数量因子的最简单方法:

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

2

这是一个示例,如果您想使用质数更快。这些列表很容易在Internet上找到。我在代码中添加了注释。

# http://primes.utm.edu/lists/small/10000.txt
# First 10000 primes

_PRIMES = (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, 101, 103, 107, 109, 113, 
        127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 
        179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 
        233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 
        283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 
        353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 
        419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 
        467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 
        547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 
        607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 
        661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 
        739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 
        811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 
        877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 
        947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 
# Mising a lot of primes for the purpose of the example
)


from bisect import bisect_left as _bisect_left
from math import sqrt as _sqrt


def get_factors(n):
    assert isinstance(n, int), "n must be an integer."
    assert n > 0, "n must be greather than zero."
    limit = pow(_PRIMES[-1], 2)
    assert n <= limit, "n is greather then the limit of {0}".format(limit)
    result = set((1, n))
    root = int(_sqrt(n))
    primes = [t for t in get_primes_smaller_than(root + 1) if not n % t]
    result.update(primes)  # Add all the primes factors less or equal to root square
    for t in primes:
        result.update(get_factors(n/t))  # Add all the factors associted for the primes by using the same process
    return sorted(result)


def get_primes_smaller_than(n):
    return _PRIMES[:_bisect_left(_PRIMES, n)]

我在Github上创建了一个项目:github.com/Pierre-Thibault/Factor
皮埃尔·锡伯

2

一种可能比此处介绍的算法更有效的算法(尤其是如果其中的主要因素较少时n)。诀窍是调整每次发现主要因素时需要进行试验划分的限制

def factors(n):
    '''
    return prime factors and multiplicity of n
    n = p0^e0 * p1^e1 * ... * pk^ek encoded as
    res = [(p0, e0), (p1, e1), ..., (pk, ek)]
    '''

    res = []

    # get rid of all the factors of 2 using bit shifts
    mult = 0
    while not n & 1:
        mult += 1
        n >>= 1
    if mult != 0:
        res.append((2, mult))

    limit = round(sqrt(n))
    test_prime = 3
    while test_prime <= limit:
        mult = 0
        while n % test_prime == 0:
            mult += 1
            n //= test_prime
        if mult != 0:
            res.append((test_prime, mult))
            if n == 1:              # only useful if ek >= 3 (ek: multiplicity
                break               # of the last prime) 
            limit = round(sqrt(n))  # adjust the limit
        test_prime += 2             # will often not be prime...
    if n != 1:
        res.append((n, 1))
    return res

当然,这仍然是审判部门,仅此而已。因此,其效率仍然非常有限(尤其是对于没有小除数的大量用户)。

这是python3; 划分//应该是您唯一需要适应python 2(add from __future__ import division)的东西。


1

使用set(...)会使代码稍微慢一些,并且仅在检查平方根时才真正需要。这是我的版本:

def factors(num):
    if (num == 1 or num == 0):
        return []
    f = [1]
    sq = int(math.sqrt(num))
    for i in range(2, sq):
        if num % i == 0:
            f.append(i)
            f.append(num/i)
    if sq > 1 and num % sq == 0:
        f.append(sq)
        if sq*sq != num:
            f.append(num/sq)
    return f

if sq*sq != num:对于12之类的数字,条件是必需的,其中平方根不是整数,但是平方根的底数是一个因子。

请注意,此版本本身不返回数字,但是如果需要,可以轻松解决。输出也未排序。

我将其定为在所有数字1-200上运行10000次,并在所有数字1-5000上运行100次。它的性能优于我测试过的所有其他版本,包括dansalmo,Jason Schorn,oxrock,agf,steveha和eryksun的解决方案,尽管oxrock最接近。


1

您的最大因数不超过您的数字,所以,假设

def factors(n):
    factors = []
    for i in range(1, n//2+1):
        if n % i == 0:
            factors.append (i)
    factors.append(n)

    return factors

瞧!


1
 import math

    '''
    I applied finding prime factorization to solve this. (Trial Division)
    It's not complicated
    '''


    def generate_factors(n):
        lower_bound_check = int(math.sqrt(n))  # determine lowest bound divisor range [16 = 4]
        factors = set()  # store factors
        for divisors in range(1, lower_bound_check + 1):  # loop [1 .. 4]
            if n % divisors == 0:
                factors.add(divisors)  # lower bound divisor is found 16 [ 1, 2, 4]
                factors.add(n // divisors)  # get upper divisor from lower [ 16 / 1 = 16, 16 / 2 = 8, 16 / 4 = 4]
        return factors  # [1, 2, 4, 8 16]


    print(generate_factors(12)) # {1, 2, 3, 4, 6, 12} -> pycharm output

 Pierre Vriens hopefully this makes more sense. this is an O(nlogn) solution. 

0

使用与以下列表推导一样简单的方法,请注意,我们不需要测试1和我们要查找的数字:

def factors(n):
    return [x for x in range(2, n//2+1) if n%x == 0]

关于平方根的使用,假设我们要查找10的因数。sqrt(10) = 4因此range(1, int(sqrt(10))) = [1, 2, 3, 4],求4 的整数部分显然未命中5。

除非我想念什么,否则我建议您使用,如果您必须这样做int(ceil(sqrt(x)))。当然,这会产生很多不必要的函数调用。


该解决方案的问题在于,它检查了许多可能不是因数的数字,并且在找到较小的因数对之后,当您已经知道它是一个因数时,它将分别检查每个因数对中的较高者。
2013年

1
@JasonSchorn:找到2时,您立即知道10/2 = 5也是除数,因此无需分别检查5!:)
Moberg

0

我认为为了提高可读性和速度,@ oxrock的解决方案是最好的,所以这里是为python 3+重写的代码:

def num_factors(n):
    results = set()
    for i in range(1, int(n**0.5) + 1):
        if n % i == 0: results.update([i,int(n/i)])
    return results

0

当我看到这个问题,即使numpy 比python循环快得多的时候,也没有人使用numpy,我感到非常惊讶。通过使用numpy实现@agf的解决方案,结果平均8倍。我相信,如果您以numpy实施其他一些解决方案,您将获得美好的时光。

这是我的功能:

import numpy as np
def b(n):
    r = np.arange(1, int(n ** 0.5) + 1)
    x = r[np.mod(n, r) == 0]
    return set(np.concatenate((x, n / x), axis=None))   

请注意,x轴的数字不是功能的输入。函数的输入为2,x轴上的数字为负1。因此,输入十是2 ** 10-1 = 1023

使用numpy代替for循环的性能测试结果。


1
如果您要使用一个库,也可以将其设为正确的库:SymPy,如Evgeni Sergeev的回答所示。
Ry-

0
import 'dart:math';
generateFactorsOfN(N){
  //determine lowest bound divisor range
  final lowerBoundCheck = sqrt(N).toInt();
  var factors = Set<int>(); //stores factors
  /**
   * Lets take 16:
   * 4 = sqrt(16)
   * start from 1 ...  4 inclusive
   * check mod 16 % 1 == 0?  set[1, (16 / 1)]
   * check mod 16 % 2 == 0?  set[1, (16 / 1) , 2 , (16 / 2)]
   * check mod 16 % 3 == 0?  set[1, (16 / 1) , 2 , (16 / 2)] -> unchanged
   * check mod 16 % 4 == 0?  set[1, (16 / 1) , 2 , (16 / 2), 4, (16 / 4)]
   *
   *  ******************* set is used to remove duplicate
   *  ******************* case 4 and (16 / 4) both equal to 4
   *  return factor set<int>.. this isn't ordered
   */

  for(var divisor = 1; divisor <= lowerBoundCheck; divisor++){
    if(N % divisor == 0){
      factors.add(divisor);
      factors.add(N ~/ divisor); // ~/ integer division 
    }
  }
  return factors;
}

这里几乎所有的算法都将范围限制为* .5,但是实际上该范围要小得多。它的实际平方数。如果我们有较低的除数,我们可以很容易地得到他的较高的除数。因为它只是数字/除数。对于16,我得到4的sqrt,然后从1到4循环。由于2是16的下界除数,我们取16/2得到8。如果我们有1,那么得到16是(16/1)。我在学习素因数分解时提出了这一点,所以我不知道它是否在其他地方发表,但即使对于大量人也可以使用。我可以提供python解决方案。
Tangang Atanga '18

-4

我认为这是最简单的方法:

    x = 23

    i = 1
    while i <= x:
      if x % i == 0:
        print("factor: %s"% i)
      i += 1

您的答案虽然给出正确的结果,但效率很低。查看接受的答案。有关如何解决问题的说明始终可以使答案更有用。
尼克,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.