web-dev-qa-db-fra.com

Quel est le moyen le plus efficace de trouver tous les facteurs d’un nombre en Python?

Est-ce que quelqu'un peut m'expliquer un moyen efficace de trouver tous les facteurs d'un nombre dans Python (2.7)?

Je peux créer des algorithmes pour faire ce travail, mais je pense qu'il est mal codé et prend trop de temps pour exécuter un résultat pour un grand nombre.

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

Ceci renverra très rapidement tous les facteurs d’un nombre n.

Pourquoi la racine carrée comme limite supérieure?

sqrt(x) * sqrt(x) = x. Donc, si les deux facteurs sont les mêmes, ils sont tous deux la racine carrée. Si vous agrandissez un facteur, vous devez réduire l’autre. Cela signifie que l'un des deux sera toujours inférieur ou égal à sqrt(x). Vous n'avez donc qu'à chercher jusqu'à ce point pour trouver l'un des deux facteurs correspondants. Vous pouvez ensuite utiliser x / fac1 pour obtenir fac2.

reduce(list.__add__, ...) prend les petites listes de [fac1, fac2] et les réunit dans une longue liste.

[i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0 renvoie une paire de facteurs si le reste lorsque vous divisez n par le plus petit est zéro (il n'est pas nécessaire de vérifier le plus grand aussi; il l'obtient simplement en divisant n par le plus petit.)

set(...) à l'extérieur supprime les doublons, ce qui n'arrive que pour les carrés parfaits. Pour n = 4, ceci retournera 2 deux fois, donc set supprimera l'un d'entre eux.

220
agf

La solution présentée par @agf est excellente, mais vous pouvez obtenir un temps d'exécution environ 50% plus rapide pour un nombre arbitraire odd en vérifiant la parité. Comme les facteurs d’un nombre impair sont toujours eux-mêmes impairs, il n’est pas nécessaire de les vérifier lorsqu’il s’agit de nombres impairs.

Je viens juste de commencer à résoudre Project Euler me pose des problèmes. Dans certains cas, une vérification de diviseur est appelée à l’intérieur de deux boucles for imbriquées et la performance de cette fonction est donc essentielle.

En combinant ce fait avec l'excellente solution d'agf, j'ai abouti à cette fonction:

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

Toutefois, sur de petits nombres (~ <100), les frais généraux supplémentaires liés à cette modification peuvent entraîner un allongement de la durée de la fonction.

J'ai fait des tests pour vérifier la vitesse. Ci-dessous le code utilisé. Pour produire les différentes parcelles, j'ai modifié la X = range(1,100,1) en conséquence.

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

Aucune différence significative ici, mais avec des nombres plus importants, l’avantage est évident:

X = étendue (1.100.000.1000) (uniquement des nombres impairs)X = range(1,100000,1000) (only odd numbers)

X = plage (2 100 000 100) (uniquement des nombres pairs)X = range(2,100000,100) (only even numbers)

X = étendue (1.100000.1001) (parité alternative)X = range(1,100000,1001) (alternating parity)

43
Steinar Lima

la réponse d'agf est vraiment très cool. Je voulais voir si je pouvais le réécrire pour éviter d'utiliser reduce(). Voici ce que je suis venu avec:

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

J'ai aussi essayé une version qui utilise des fonctions de générateur délicates:

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)

Je l'ai chronométré en calculant:

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

Je l'ai exécuté une fois pour laisser Python le compiler, puis sous la commande time (1) trois fois et j'ai gardé le meilleur temps.

  • version réduite: 11.58 secondes
  • version d'itertools: 11.49 secondes
  • version délicate: 11,12 secondes

Notez que la version d'itertools construit un tuple et le passe à flatten_iter (). Si je change le code pour construire une liste à la place, cela ralentit légèrement:

  • iterools (list) version: 11.62 secondes

Je pense que la version délicate des fonctions du générateur est la plus rapide possible en Python. Mais ce n'est pas vraiment beaucoup plus rapide que la version réduite, environ 4% plus rapide en fonction de mes mesures.

26
steveha

Une approche alternative à la réponse de 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

Pour n jusqu’à 10 ** 16 (peut-être même un peu plus), voici une solution rapide et pure 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
Bruno Astrolino

Autres améliorations apportées à la solution de afg & eryksun . Le code suivant renvoie une liste triée de tous les facteurs sans modifier la complexité asymptotique de l'exécution:

    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

Idée: Au lieu d’utiliser la fonction list.sort () pour obtenir une liste triée donnant la complexité de nlog (n); Il est beaucoup plus rapide d’utiliser list.reverse () sur l2 qui prend la complexité de O(n). (C'est comme ça que python est fabriqué.) Après l2.reverse (), l2 peut être ajouté à l1 pour obtenir la liste triée des facteurs.

Avis, l1 contient je-s qui sont en augmentation. l2 contient q-s qui diminuent. C'est la raison derrière l'utilisation de l'idée ci-dessus.

6
Pranjal Mittal

J'ai essayé la plupart de ces merveilleuses réponses avec le temps, en comparant leur efficacité à ma fonction simple et pourtant je vois constamment les miennes surperformer celles listées ici. Je pensais le partager et voir ce que vous pensez tous.

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

Comme il est écrit, vous devrez importer math pour tester, mais remplacer math.sqrt (n) par n **. 5 devrait également fonctionner. Je ne me donne pas la peine de perdre du temps à vérifier les doublons, car ceux-ci ne peuvent exister dans un ensemble.

6
oxrock

Voici une alternative à la solution de @ agf qui implémente le même algorithme dans un style plus pythonique:

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

Cette solution fonctionne à la fois en Python 2 et 3 sans importation et est beaucoup plus lisible. Je n'ai pas testé les performances de cette approche, mais asymptotiquement, ce devrait être la même chose. Si les performances posent un problème sérieux, aucune solution n'est optimale.

5
Julian

Voici une autre alternative sans réduction qui fonctionne bien avec un grand nombre. Il utilise sum pour aplatir la liste.

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

Assurez-vous de saisir le nombre plus grand que sqrt(number_to_factor) pour les nombres inhabituels comme 99, qui a 3 * 3 * 11 et 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
4
mbowden

Il existe un algorithme puissant dans SymPy appelé 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}

Cela a pris moins d'une minute. Il alterne entre un cocktail de méthodes. Voir la documentation liée ci-dessus.

Compte tenu de tous les facteurs premiers, tous les autres facteurs peuvent être construits facilement.


Notez que même si la réponse acceptée était autorisée à s'exécuter assez longtemps (c'est-à-dire une éternité) pour factoriser le nombre ci-dessus, elle échouera pour certains grands nombres, comme dans l'exemple suivant. Cela est dû à la int(n**0.5) bâclée. Par exemple, lorsque n = 10000000000000079**2, nous avons 

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

Comme 10000000000000079 est un nombre premier , l'algorithme de la réponse acceptée ne trouvera jamais ce facteur. Notez que ce n'est pas juste une off-by-one; pour un plus grand nombre, il y en aura plus. Pour cette raison, il est préférable d'éviter les nombres à virgule flottante dans les algorithmes de ce type.

4
Evgeni Sergeev

un algorithme potentiellement plus efficace que ceux présentés ici (surtout s’il existe de petits nombres premiers dans n). le truc ici est de ajuster la limite jusqu’à quelle division d’essai est nécessaire chaque fois que des facteurs premiers sont trouvés:

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

il s’agit bien entendu encore de la division de procès et de rien d’extraordinaire. et donc toujours très limité dans son efficacité (surtout pour les grands nombres sans petits diviseurs).

c'est python3; la division // devrait être la seule chose à adapter pour python 2 (ajoutez from __future__ import division).

2
hiro protagonist

Voici un exemple si vous souhaitez utiliser le nombre de nombres premiers pour aller beaucoup plus vite. Ces listes sont faciles à trouver sur Internet. J'ai ajouté des commentaires dans le code.

# 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

Utiliser set(...) ralentit légèrement le code et n’est vraiment nécessaire que pour vérifier la racine carrée. Voici ma 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

La condition if sq*sq != num: est nécessaire pour des nombres tels que 12, où la racine carrée n'est pas un entier, mais le plancher de la racine carrée est un facteur.

Notez que cette version ne renvoie pas le numéro lui-même, mais c'est une solution facile si vous le souhaitez. La sortie n'est pas non plus triée.

Je l'ai chronométré 10000 fois sur tous les numéros 1-200 et 100 fois sur tous les numéros 1-5000. Il surpasse toutes les autres versions que j'ai testées, y compris les solutions de dansalmo, de Jason Schorn, d'oxrock, d'agf, de steveha et d'eryksun, bien qu'oxrock soit de loin le plus proche.

1
HamsterBoo

Utilisez quelque chose d'aussi simple que la compréhension de liste suivante, en notant que nous n'avons pas besoin de tester 1 et le nombre que nous essayons de trouver:

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

En ce qui concerne l'utilisation de la racine carrée, supposons que nous recherchions un facteur de 10. La partie entière de sqrt(10) = 4 est donc range(1, int(sqrt(10))) = [1, 2, 3, 4] et le nombre de tests manquant est nettement supérieur à 5.

À moins que je ne manque quelque chose que je suggérerais, si vous devez le faire de cette façon, en utilisant int(ceil(sqrt(x))). Bien sûr, cela génère beaucoup d'appels inutiles à des fonctions.

1
Jason Schorn

votre facteur max n'est pas supérieur à votre nombre, alors disons

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

    return factors

voila!

1
Polina Novikova

Le moyen le plus simple de trouver les facteurs d’un nombre:

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

Je pense que pour la lisibilité et la rapidité, la solution de @ oxrock est la meilleure solution. Voici donc le code récrit pour 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
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

J'ai été assez surpris quand j'ai vu cette question que personne n'utilisait numpy, même si numpy est beaucoup plus rapide que les boucles en python. En implémentant la solution de @ agf avec numpy, il s'est avéré que la moyenne était de 8x plus rapide . Je pense que si vous implémentiez certaines des autres solutions numpy, vous pourriez vivre des moments incroyables.

Voici ma fonction:

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

Notez que les nombres de l’axe des x ne sont pas l’entrée des fonctions. L'entrée aux fonctions est 2 au nombre sur l'axe des x moins 1 . Donc où dix est l'entrée serait 2 ** 10-1 = 1023 

 Performance test results of using numpy instead of for loops.