簡體   English   中英

在 Python 中找到一個數的所有因子的最有效方法是什么?

[英]What is the most efficient way of finding all the factors of a number in Python?

有人可以向我解釋一種找到 Python (2.7) 中數字的所有因子的有效方法嗎?

我可以創建一個算法來執行此操作,但我認為它的編碼很差,並且需要很長時間才能為大量結果生成結果。

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除以較小的余數時的余數[i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0返回一對因子(它也不需要檢查較大的;它只是通過將n除以較小的來獲得。)

外面的set(...)正在擺脫重復,這只會發生在完美的正方形上。 對於n = 4 ,這將返回2兩次,因此set擺脫了其中之一。

@agf 提出的解決方案很棒,但是通過檢查奇偶校驗可以將任意奇數的運行時間縮短約 50%。 由於奇數的因數本身總是奇數,因此在處理奇數時無需檢查這些。

我剛剛開始自己​​解決Project Euler難題。 在某些問題中,在兩個嵌套的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 = 范圍(1,100,1)

這里沒有顯着差異,但數字越大,優勢就很明顯:

X = range(1,100000,1000)(僅奇數)X = range(1,100000,1000)(僅奇數)

X = range(2,100000,100)(僅偶數)X = range(2,100000,100)(僅偶數)

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

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)命令下跑了3次,保持最佳時間。

  • 減少版本:11.58 秒
  • itertools 版本:11.49 秒
  • 棘手版本:11.12 秒

請注意,itertools 版本正在構建一個元組並將其傳遞給 flatten_iter()。 如果我更改代碼來構建列表,它會稍微減慢速度:

  • iterools(列表)版本:11.62 秒

我相信棘手的生成器函數版本是 Python 中最快的。 但它並不比 reduce 版本快多少,根據我的測量,大約快 4%。

SymPy 中有一個行業強度的算法,稱為factorint

>>> 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 是一個質數,接受的答案的算法永遠不會找到這個因素。 請注意,這不僅僅是一對一的; 對於較大的數字,它會減少更多。 出於這個原因,最好避免在此類算法中使用浮點數。

這是@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)
    )

此解決方案適用於 Python 2 和 Python 3,無需導入,並且更具可讀性。 我還沒有測試過這種方法的性能,但漸近地它應該是一樣的,如果性能是一個嚴重的問題,那么兩種解決方案都不是最佳的。

對於高達 10**16(甚至更多)的 n,這是一個快速的純 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))

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

求一個數的因數的最簡單方法:

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

我已經用 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

正如它所寫的那樣,您必須導入數學來進行測試,但是將 math.sqrt(n) 替換為 n**.5 應該也能正常工作。 我不想浪費時間檢查重復項,因為無論如何重復項都不能存在於集合中。

進一步改進 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) 復雜度; 在需要 O(n) 復雜度的 l2 上使用 list.reverse() 要快得多。 (python 就是這樣制作的。)在 l2.reverse() 之后,可以將 l2 附加到 l1 以獲得排序的因子列表。

請注意, l1 包含i -s 正在增加。 l2 包含正在減少的q -s。 這就是使用上述想法的原因。

這是另一種沒有減少的替代方法,它在大數字上表現良好。 它使用sum來展平列表。

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

對於不尋常的數字,比如 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

如果您想使用素數來加快速度,這是一個示例。 這些列表很容易在互聯網上找到。 我在代碼中添加了注釋。

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

一種可能比這里已經介紹的算法更有效的算法(特別是如果n有小的素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 的東西( from __future__ import division添加)。

使用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 是迄今為止最接近的。

當我看到這個問題時,我感到非常驚訝,即使 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 軸的數字不是函數的輸入。 函數的輸入是 x 軸上的數字減 1 的 2。所以輸入十是 2**10-1 = 1023

使用 numpy 代替 for 循環的性能測試結果。

你的最大因素不超過你的數字,所以,讓我們說

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 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(n):
    l=[] #emoty list
    # appending the factors in the list
    for i in range(1,n+1):
        if n%i==0:
            l.append(i)

我是一名中級程序員。 所以以防萬一,如果我錯了,請糾正我

使用像以下列表推導這樣簡單的東西,注意我們不需要測試 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))) 當然,這會產生大量不必要的函數調用。

我認為為了可讀性和速度@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

循環,直到在元組的 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]

我在 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是素數,它將返回一個空數組。

考慮到數字是正數 integer,您可以使用這種方法:

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

我有點驚訝我找不到 integer 形式的素數分解的簡單實現(p1 ** e1) * (p2 ** e2)... ,所以我決定自己寫一個。

from collections import defaultdict
from itertools import count

def factorize(n):
    factors = defaultdict(int)
    for i in count(2):
        while n % i == 0:
            factors[i] += 1
            n /= i

        if n == 1:
            return factors

這個 function 返回一個字典,其中鍵是質數因子,值是指數。 例如:

>>> factorize(608)
defaultdict(<class 'int'>, {2: 5, 19: 1})
>>> factorize(1152)
defaultdict(<class 'int'>, {2: 7, 3: 2})
>>> factorize(1088)
defaultdict(<class 'int'>, {2: 6, 17: 1})

這顯然不是最有效的實現——因為它迭代整個自然數集,而不是直接針對素數——但它對於相對較小的值來說已經足夠好了,而且足夠簡單,易於理解。

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

我認為這是最簡單的方法:

    x = 23

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM