web-dev-qa-db-de.com

Was ist der effizienteste Weg, um alle Faktoren einer Zahl in Python zu finden?

Kann mir jemand einen effizienten Weg erklären, alle Faktoren einer Zahl in Python (2.7) zu finden?

Ich kann Algorithmen erstellen, um diesen Job auszuführen, aber ich denke, dass er schlecht codiert ist und zu lange dauert, um ein Ergebnis für eine große Anzahl auszuführen.

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

Dadurch werden sehr schnell alle Faktoren einer Zahl nzurückgegeben.

Warum Quadratwurzel als Obergrenze?

sqrt(x) * sqrt(x) = x. Wenn also die beiden Faktoren gleich sind, sind sie beide die Quadratwurzel. Wenn Sie einen Faktor größer machen, müssen Sie den anderen Faktor kleiner machen. Dies bedeutet, dass einer der beiden Werte immer kleiner oder gleich sqrt(x) ist. Sie müssen also nur bis zu diesem Punkt suchen, um einen der beiden Übereinstimmungsfaktoren zu finden. Sie können dann x / fac1 verwenden, um fac2 zu erhalten.

Die reduce(list.__add__, ...) nimmt die kleinen Listen von [fac1, fac2] und fügt sie zu einer langen Liste zusammen.

Die Funktion [i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0 gibt ein Paar von Faktoren zurück, wenn der Rest beim Teilen von ndurch den kleineren Null ist (es muss nicht auch der größere überprüft werden; dies wird nur durch Teilen von ndurch den kleineren erreicht.)

Die set(...) auf der Außenseite beseitigt Duplikate, was nur bei perfekten Quadraten der Fall ist. Bei n = 4 wird 2 zweimal zurückgegeben, sodass seteinen davon entfernt.

242
agf

Die von @agf präsentierte Lösung ist großartig, aber Sie können eine um ~ 50% kürzere Laufzeit für eine beliebige ungerade - Zahl erreichen, indem Sie auf Parität prüfen. Da die Faktoren einer ungeraden Zahl selbst immer ungerade sind, müssen diese bei ungeraden Zahlen nicht überprüft werden.

Ich habe gerade angefangen, Project Euler Rätsel selbst zu lösen. Bei einigen Problemen wird eine Divisor-Prüfung in zwei verschachtelten for-Schleifen aufgerufen. Die Ausführung dieser Funktion ist daher unerlässlich.

Durch die Kombination dieser Tatsache mit der hervorragenden Lösung von agf erhielt ich diese Funktion:

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

Bei kleinen Zahlen (~ <100) kann der zusätzliche Aufwand dieser Änderung dazu führen, dass die Funktion länger dauert.

Ich habe ein paar Tests gemacht, um die Geschwindigkeit zu überprüfen. Unten ist der verwendete Code. Um die verschiedenen Darstellungen zu erstellen, habe ich die X = range(1,100,1) entsprechend geändert.

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 = Bereich (1.100,1)X = range(1,100,1)

Kein wesentlicher Unterschied, aber bei größeren Zahlen liegt der Vorteil auf der Hand:

X = Bereich (1.100000.1000) (nur ungerade Zahlen)X = range(1,100000,1000) (only odd numbers)

X = Bereich (2.100000.100) (nur gerade Zahlen)X = range(2,100000,100) (only even numbers)

X = Bereich (1,100000,1001) (alternierende Parität)X = range(1,100000,1001) (alternating parity)

43
Steinar Lima

die Antwort von agf ist wirklich ziemlich cool. Ich wollte sehen, ob ich es neu schreiben könnte, um reduce() zu vermeiden. Das ist was ich mir ausgedacht habe:

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

Ich habe auch eine Version ausprobiert, die komplizierte Generatorfunktionen verwendet:

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)

Ich habe es mit der Berechnung des Zeitablaufs durchgeführt:

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

Ich habe es einmal ausgeführt, um Python kompilieren zu lassen, und dann dreimal mit dem Befehl time (1) ausgeführt.

  • version reduzieren: 11,58 Sekunden
  • itertools-Version: 11,49 Sekunden
  • knifflige Version: 11,12 Sekunden

Beachten Sie, dass die itertools-Version einen Tuple erstellt und an flatten_iter () weitergibt. Wenn ich stattdessen den Code zum Erstellen einer Liste ändere, verlangsamt es sich geringfügig:

  • iterools (list) version: 11.62 Sekunden

Ich glaube, dass die knifflige Generatorfunktionsversion in Python die schnellstmögliche ist. Es ist jedoch nicht viel schneller als die reduzierte Version, ungefähr 4% schneller, basierend auf meinen Messungen.

26
steveha

Ein alternativer Ansatz zur Antwort von 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
11
eryksun

Für n bis zu 10 ** 16 (vielleicht sogar etwas mehr) gibt es hier eine schnelle reine Python 3.6-Lösung.

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
Bruno Astrolino

Weitere Verbesserung der Lösung von afg & eryksun ..__ Der folgende Code gibt eine sortierte Liste aller Faktoren zurück, ohne die asymptotische Komplexität der Laufzeit zu ändern:

    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

Idee: Anstatt die Funktion list.sort () zu verwenden, um eine sortierte Liste zu erhalten, die Komplexität von nlog (n) ergibt. Es ist viel schneller, list.reverse () auf l2 zu verwenden, was O(n) Komplexität erfordert. (So ​​wird Python erstellt.) Nach l2.reverse () kann l2 an l1 angehängt werden, um die sortierte Liste der Faktoren zu erhalten.

Beachten Sie, dass l1 enthält ich-s die zunehmen. l2 enthält q-s die abnehmen. Das ist der Grund hinter der obigen Idee.

6
Pranjal Mittal

Ich habe die meisten dieser wunderbaren Antworten mit der Zeit ausprobiert, um ihre Effizienz mit meiner einfachen Funktion zu vergleichen, und trotzdem sehe ich meine immer besser als die hier aufgeführten. Ich dachte mir, ich würde es teilen und sehen, was Sie alle denken.

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

Wie es geschrieben wurde, müssen Sie zum Testen Mathematik importieren, aber math.sqrt (n) durch n **. 5 zu ersetzen, sollte genauso gut funktionieren. Ich mache mir nicht die Mühe, Zeit für Dubletten zu suchen, da Dubletten nicht in einem Set existieren können.

6
oxrock

Hier ist eine Alternative zur @ agf-Lösung, die denselben Algorithmus in einem eher pythonischen Stil implementiert:

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

Diese Lösung funktioniert in Python 2 und Python 3 ohne Importe und ist viel lesbarer. Ich habe die Leistung dieses Ansatzes nicht getestet, aber asymptotisch sollte es die gleiche sein, und wenn die Leistung ein ernstes Problem darstellt, ist keine der Lösungen optimal.

5
Julian

Hier ist eine weitere Alternative ohne zu reduzieren, die bei großen Zahlen gut funktioniert. Es verwendet sum, um die Liste zu reduzieren.

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

Achten Sie darauf, die Nummer größer als sqrt(number_to_factor) für ungewöhnliche Nummern wie 99 zu ermitteln, die 3 * 3 * 11 und floor sqrt(99)+1 == 10 hat.

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
4
mbowden

In SymPy gibt es einen branchenfesten Algorithmus mit dem Namen 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}

Dies dauerte weniger als eine Minute. Es wechselt zwischen einem Methodencocktail. Siehe die oben verlinkte Dokumentation.

Unter Berücksichtigung aller Primfaktoren können alle anderen Faktoren leicht aufgebaut werden.


Es sei angemerkt, dass selbst wenn die akzeptierte Antwort lange genug laufen gelassen werden konnte (d. H. Eine Ewigkeit), um die obige Zahl zu faktorisieren, für einige große Zahlen fehlschlägt, wie im folgenden Beispiel. Dies ist auf das schlampige int(n**0.5) zurückzuführen. Zum Beispiel, wenn n = 10000000000000079**2, haben wir 

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

Da 10000000000000079 eine Primzahl ist, wird der akzeptierte Antwortalgorithmus diesen Faktor niemals finden. Beachten Sie, dass es sich nicht nur um ein Einzelstück handelt. für größere Zahlen wird es um mehr sein. Aus diesem Grund sollten Gleitkommazahlen in solchen Algorithmen besser vermieden werden.

4
Evgeni Sergeev

ein potentiell effizienterer Algorithmus als die hier bereits vorgestellten (insbesondere wenn in n kleine Primfaktoren vorhanden sind). Der Trick besteht hier darin, das Limit anzupassen bis zu welcher Probeabteilung jedes Mal benötigt wird, wenn Primfaktoren gefunden werden:

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

dies ist natürlich noch eine Probeabteilung und nichts Besonderes. und daher immer noch sehr eingeschränkt in der Effizienz (insbesondere für große Zahlen ohne kleine Teiler).

das ist Python3; Die Division // sollte das einzige sein, das Sie für Python 2 anpassen müssen (add from __future__ import division).

2

Hier ein Beispiel, wenn Sie die Primzahl verwenden möchten, um viel schneller zu gehen. Diese Listen sind im Internet leicht zu finden. Ich habe im Code Kommentare hinzugefügt.

# 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)]
2
Pierre Thibault

Die Verwendung von set(...) macht den Code etwas langsamer und ist nur dann wirklich notwendig, wenn Sie die Quadratwurzel überprüfen. Hier ist meine Version:

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

Die if sq*sq != num:-Bedingung ist für Zahlen wie 12 erforderlich, bei denen die Quadratwurzel keine Ganzzahl ist, der Boden der Quadratwurzel jedoch ein Faktor ist.

Beachten Sie, dass diese Version die Nummer selbst nicht zurückgibt. Dies ist jedoch eine einfache Lösung, wenn Sie möchten. Die Ausgabe wird auch nicht sortiert.

Ich habe es 10000 Mal auf allen Nummern 1-200 und 100 Mal auf allen Nummern 1-5000 ausgeführt. Es übertrifft alle anderen Versionen, die ich getestet habe, einschließlich der Lösungen von Dansalmo, Jason Schorn, Oxrock, Agf, Steveha und Eryksun, obwohl Oxrock bei weitem die nächsten ist.

1
HamsterBoo

Verwenden Sie etwas, das so einfach ist wie das folgende Listenverständnis, und beachten Sie, dass wir nicht 1 testen müssen und die Nummer, die wir zu finden versuchen:

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

In Bezug auf die Verwendung der Quadratwurzel wollen wir beispielsweise Faktoren von 10 finden. Der ganzzahlige Teil der sqrt(10) = 4-Funktion range(1, int(sqrt(10))) = [1, 2, 3, 4] und das Testen von bis zu 4 fehlen somit eindeutig 5.

Wenn ich etwas nicht vermisse, würde ich vorschlagen, wenn Sie es auf diese Weise tun müssen, verwenden Sie int(ceil(sqrt(x))). Dies führt natürlich zu unnötigen Funktionsaufrufen.

1
Jason Schorn

ihr maximaler Faktor ist nicht mehr als Ihre Zahl, sagen wir mal

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

    return factors

voilá!

1
Polina Novikova

Der einfachste Weg, Faktoren einer Zahl zu finden:

def factors(x):
    return [i for i in range(1,x+1) if x%i==0]
0
GooDeeJaY
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;
}
0
Tangang Atanga

Ich denke, für Lesbarkeit und Geschwindigkeit ist die Lösung von @ oxrock die beste, daher wird hier der Code für Python 3+ neu geschrieben:

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
Nic Scozzaro
 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
Tangang Atanga

Ich war ziemlich überrascht, als ich diese Frage sah, dass niemand numpy verwendet hat, selbst wenn numpy viel schneller als Python-Loops ist. Indem Sie die @ agf-Lösung mit numpy implementieren, stellte sich heraus, dass der Durchschnitt 8x schneller..__ ist. Ich glaube, wenn Sie einige der anderen Lösungen in numpy implementieren, können Sie erstaunliche Zeiten erzielen.

Hier ist meine Funktion:

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

Beachten Sie, dass die Zahlen der x-Achse nicht die Eingabe für die Funktionen sind. Die Eingabe für die Funktionen ist 2 bis die Zahl auf der x-Achse minus 1 . Wenn also zehn ist, wäre die Eingabe 2 ** 10-1 = 1023 

 Performance test results of using numpy instead of for loops.