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

有人能给我解释一下在 Python (2.7)中找到一个数的所有因子的有效方法吗?

我可以创建一个算法来做到这一点,但我认为这是差劲的编码,需要太长的时间才能产生一个大量的结果。

246638 次浏览
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]和加入他们在一起的一个长名单。

如果将 n除以较小的因子的余数为零,则 [i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0返回一对因子(它不需要检查较大的因子; 它只需将 n除以较小的因子即可得到该因子)

外面的 set(...)正在消除重复,这只发生在完美的正方形上。对于 n = 4,这将返回 2两次,所以 set去掉其中一个。

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

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 版本正在构建一个 tuple 并将其传递给 flatten _ iter ()。如果我改变代码来构建一个列表,它会稍微慢一点:

  • Iterools (list)版本: 11.62秒

我相信生成器函数的复杂版本是 Python 中最快的。但是它并没有比降低版本快多少,根据我的测量,大约快了4% 。

进一步改进爱克森公司的解决方案。 下面的代码返回所有因素的排序列表,而不改变运行时的渐近复杂度:

    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)复杂性的排序列表; 在带有 O (n)复杂性的 l2上使用 list.return ()要快得多。(蟒蛇就是这样被制造出来的。) 在 l2.return ()之后,可以将 l2附加到 l1以获得排序的因子列表。

注意,l1包含的 -s 正在增加。L2含有正在减少的 -s。这就是使用上述想法的原因。

对于不寻常的数字,如99,它有3 * 3 * 11和 floor sqrt(99)+1 == 10,一定要选择大于 sqrt(number_to_factor)的数字。

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和我们试图找到的数字:

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)))。当然,这会产生大量不必要的函数调用。

@ agf 提供的解决方案非常好,但是通过检查奇偶校验,可以为任意 奇怪数目提供约50% 的运行时间。由于奇数的因子本身总是奇数,所以在处理奇数时没有必要检查这些因子。

我刚开始自己解 欧拉计划的谜题。在某些问题中,在两个嵌套的 for循环中调用一个除数检查,因此这个函数的性能是必不可少的。

将这个事实与 agf 的优秀解决方案结合起来,我得到了这个函数:

from functools import reduce
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 = range(1,100,1)

这里没有显著差异,但数字越大,优势就越明显:

X = 范围(1,100000,1000)(只有奇数) X = range(1,100000,1000) (only odd numbers)

X = 范围(2,100000,100)(只有偶数) X = range(2,100000,100) (only even numbers)

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

下面是另一个不用 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], []))

随着时间的推移,我已经尝试了这些绝妙的答案中的大多数,来比较它们的效率和我的简单功能,然而我经常看到我的表现胜过这里列出的那些。我想我应该分享一下,看看你们怎么想。

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)应该也可以。我不会浪费时间检查重复项,因为重复项无论如何都不能存在于一个集合中。

这里有一个例子,如果你想使用质数去更快。这些名单在网上很容易找到。我在代码中添加了注释。

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

使用 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

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

注意,这个版本不返回数字本身,但是如果您想要的话,这是一个简单的修复。输出也没有排序。

我对所有数字1-200计时10000次,对所有数字1-5000计时100次。它比我测试过的所有其他版本都要好,包括 dansalmo’s,Jason Schorn’s,oxrock’s,agf’s,steveha’s,和 eryksun’s Solutions,尽管 oxrock’s 是目前为止最接近的。

这里有一个替代@agf 的解决方案,它以一种更加简洁的风格实现了相同的算法:

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

这个解决方案在 Python2和 Python3中都可以工作,不需要导入,而且更具可读性。我还没有测试过这种方法的性能,但是渐近地它应该是相同的,如果性能是一个严重的问题,那么这两种解决方案都不是最优的。

我认为就可读性和 speed@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

一个潜在的更有效的算法比这里已经提出的(特别是如果有小的质因子在 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; 除 //应该是唯一需要为 python2调整的部分(添加 from __future__ import division)。

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

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

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

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

    x = 23


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

当我看到这个问题时,我非常惊讶,没有人使用 numpy,即使 numpy 是 快多了而不是 python 循环。通过使用 numpy 实现@agf 的解决方案,结果是平均 快了8倍。 我相信,如果你实现了一些其他的解决方案在麻木,你可以得到惊人的时间。

我的功能是:

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 轴的数字不是函数的输入。函数的输入是 x 轴减1的数字的2次方。 所以十的输入是2 * * 10-1 = 1023

Performance test results of using numpy instead of for loops.

你的最大系数不超过你的数字,所以,我们假设

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


return factors

瞧!

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;
}
 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.

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

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

循环,直到在元组的 x 或 v 中找到重复项,其中 x 是分母,v 是结果。

number=30
tuple_list=[]
for i in np.arange(1,number):
if number%i==0:
other=int(number/i)
if any([(x,v) for (x,v) in tuple_list if (i==x) or (i==v)])==True:
break
tuple_list.append((i,other))
    

flattened = [item for sublist in tuple_list for item in sublist]
print(sorted(flattened))

输出

 [1, 2, 3, 5, 6, 10, 15, 30]

如果你不想使用任何库,那么我认为这是最简单的方法:

def factors(n):
l = [] # empty list
# appending the factors in the list
for i in range(1,n+1):
if n%i==0:
l.append(i)
return l

我在 python 中找到了一个使用 cypari 库的简单解决方案。 这里是 一种联系

import cypari
def get_divisors(n):
divisors = cypari.pari('divisors({})'.format(n))
return divisors
print(get_divisors(24))

输出

[1, 2, 3, 4, 6, 8, 12, 24]

我们可以使用下面的 lambda 函数,

factor = lambda x:[(ele,x/ele) for ele in range(1,x//2+1) if x%ele==0 ]

因素(10)

输出: [(1,10.0) ,(2,5.0) ,(5,2.0)]

此函数返回列表中给定数字的所有因子。

虽然问题是 Python (2.7) ,但人们可能对这个使用 Numpy 的简单解决方案感兴趣。

import numpy as np


t=np.arange(2,n,1)
t[n%t==0]

这不会返回 1,也不会返回数字本身 n。因此,如果 n是质数,它将返回一个空数组。

考虑到数字是正整数,您可以使用这种方法:

number = int(input("Enter a positive number to find factors: "))
factor = [num for num in range(1,number+1) if number % num == 0]
for fac in factor: print(f"{fac} is a factor of {number}")