web-dev-qa-db-fra.com

Quel est le meilleur moyen d’obtenir tous les diviseurs d’un nombre?

Voici la manière très stupide:

def divisorGenerator(n):
    for i in xrange(1,n/2+1):
        if n%i == 0: yield i
    yield n

Le résultat que je voudrais obtenir est similaire à celui-ci, mais je voudrais un algorithme plus intelligent (celui-ci est trop lent et stupide :-)

Je peux trouver les facteurs premiers et leur multiplicité assez rapidement. J'ai un générateur qui génère un facteur de cette façon:

(facteur1, multiplicité1)
(facteur2, multiplicité2)
(facteur3, multiplicité3)
etc...

c'est-à-dire la sortie de 

for i in factorGenerator(100):
    print i

est:

(2, 2)
(5, 2)

Je ne sais pas à quel point est-ce utile pour ce que je veux faire (je l'ai codé pour d'autres problèmes), de toute façon, je voudrais une façon plus intelligente de faire

for i in divisorGen(100):
    print i

afficher ceci:

1
2
4
5
10
20
25
50
100

UPDATE: Merci beaucoup à Greg Hewgill et à sa "façon intelligente":) Calculer tous les diviseurs de 100000000 a pris 0.01s avec son chemin contre les 39s que la manière stupide a pris sur ma machine, très cool:

UPDATE 2: Cesser de dire que ceci est une copie de this post. Le calcul du nombre de diviseurs d'un nombre donné n'a pas besoin de calculer tous les diviseurs. C'est un problème différent, si vous pensez que ce n'est pas le cas, cherchez «fonction de diviseur» sur Wikipedia. Lisez les questions et la réponse avant de poster, si vous ne comprenez pas quel est le sujet, n’ajoutez pas de réponses inutiles et déjà données.

86
Andrea Ambu

Étant donné votre fonction factorGenerator, voici un divisorGen qui devrait fonctionner:

def divisorGen(n):
    factors = list(factorGenerator(n))
    nfactors = len(factors)
    f = [0] * nfactors
    while True:
        yield reduce(lambda x, y: x*y, [factors[x][0]**f[x] for x in range(nfactors)], 1)
        i = 0
        while True:
            f[i] += 1
            if f[i] <= factors[i][1]:
                break
            f[i] = 0
            i += 1
            if i >= nfactors:
                return

L'efficacité globale de cet algorithme dépendra entièrement de l'efficacité du factorGenerator.

69
Greg Hewgill

Pour développer ce que Shimi a dit, vous ne devriez exécuter votre boucle que de 1 à la racine carrée de n. Ensuite, pour trouver la paire, faites n / i, et cela couvrira tout l'espace du problème.

Comme il a également été noté, il s’agit d’un problème NP ou «difficile». La recherche exhaustive, telle que vous la faites, est à peu près aussi bonne qu'elle obtient des réponses garanties. Ce fait est utilisé par les algorithmes de chiffrement et autres pour les sécuriser. Si quelqu'un devait résoudre ce problème, la plupart, sinon toutes les communications «sécurisées» actuelles seraient rendues non sécurisées.

Code python:

import math

def divisorGenerator(n):
    large_divisors = []
    for i in xrange(1, int(math.sqrt(n) + 1)):
        if n % i == 0:
            yield i
            if i*i != n:
                large_divisors.append(n / i)
    for divisor in reversed(large_divisors):
        yield divisor

print list(divisorGenerator(100))

Ce qui devrait sortir une liste comme:

 [1, 2, 4, 5, 10, 20, 25, 50, 100] 
32
Matthew Scharley

Bien qu'il y ait déjà beaucoup de solutions à cela, je dois vraiment poster ça :)

Celui-ci est:

  • lisible 
  • court
  • autonome, prêt à copier et coller
  • rapide (dans les cas comportant beaucoup de facteurs premiers et de diviseurs,> 10 fois plus rapide que la solution acceptée)
  • conforme à python3, python2 et pypy

Code:

def divisors(n):
    # get factors and their counts
    factors = {}
    nn = n
    i = 2
    while i*i <= nn:
        while nn % i == 0:
            factors[i] = factors.get(i, 0) + 1
            nn //= i
        i += 1
    if nn > 1:
        factors[nn] = factors.get(nn, 0) + 1

    primes = list(factors.keys())

    # generates factors from primes[k:] subset
    def generate(k):
        if k == len(primes):
            yield 1
        else:
            rest = generate(k+1)
            prime = primes[k]
            for factor in rest:
                prime_to_i = 1
                # prime_to_i iterates prime**i values, i being all possible exponents
                for _ in range(factors[prime] + 1):
                    yield factor * prime_to_i
                    prime_to_i *= prime

    # in python3, `yield from generate(0)` would also work
    for factor in generate(0):
        yield factor
13
Tomas Kulich

Je pense que vous pouvez vous arrêter à math.sqrt(n) au lieu de n/2. 

Je vais vous donner un exemple afin que vous puissiez le comprendre facilement. Maintenant, la sqrt(28) est 5.29 donc ceil(5.29) sera 6. Donc, si je vais m'arrêter à 6 heures, je pourrai obtenir tous les diviseurs. Comment?

Tout d'abord voir le code et ensuite voir l'image:

import math
def divisors(n):
    divs = [1]
    for i in xrange(2,int(math.sqrt(n))+1):
        if n%i == 0:
            divs.extend([i,n/i])
    divs.extend([n])
    return list(set(divs))

Maintenant, voir l'image ci-dessous:

Disons que j'ai déjà ajouté 1 à ma liste de diviseurs et que je commence par i=2, donc

 Divisors of a 28

Donc, à la fin de toutes les itérations car j'ai ajouté le quotient et le diviseur à ma liste, tous les diviseurs de 28 sont remplis. 

J'espère que cela t'aides. Si vous avez des doutes n'hésitez pas à revenir en arrière et je me ferai un plaisir de vous aider :).

Source: Comment déterminer les diviseurs d'un nombre
Rayon de cercle - Code et image

12
Anivarth

J'aime la solution de Greg, mais j'aimerais que ce soit plus en python, comme… .. Je pense que ce serait plus rapide et plus lisible; donc après un certain temps de codage, je suis sorti avec ceci.

Les deux premières fonctions sont nécessaires pour créer le produit cartésien des listes. Et peut être réutilisé quel que soit le problème. Au fait, je devais programmer moi-même, si quelqu'un connaissait une solution standard à ce problème, n'hésitez pas à me contacter.

"Factorgenerator" renvoie maintenant un dictionnaire. Et puis le dictionnaire est introduit dans les "diviseurs", qui l'utilisent pour générer d'abord une liste de listes, où chaque liste est la liste des facteurs de la forme p ^ n avec p prime . Ensuite, nous créons le produit cartésien. de cette liste, et nous avons finalement utilisé la solution de Greg pour générer le diviseur . Nous les avons triés et renvoyés.

Je l'ai testé et il semble être un peu plus rapide que la version précédente. Je l'ai testé dans le cadre d'un programme plus important, donc je ne peux pas vraiment dire à quel point c'est plus rapide.

Pietro Speroni (pietrosperoni)

from math import sqrt


##############################################################
### cartesian product of lists ##################################
##############################################################

def appendEs2Sequences(sequences,es):
    result=[]
    if not sequences:
        for e in es:
            result.append([e])
    else:
        for e in es:
            result+=[seq+[e] for seq in sequences]
    return result


def cartesianproduct(lists):
    """
    given a list of lists,
    returns all the possible combinations taking one element from each list
    The list does not have to be of equal length
    """
    return reduce(appendEs2Sequences,lists,[])

##############################################################
### prime factors of a natural ##################################
##############################################################

def primefactors(n):
    '''lists prime factors, from greatest to smallest'''  
    i = 2
    while i<=sqrt(n):
        if n%i==0:
            l = primefactors(n/i)
            l.append(i)
            return l
        i+=1
    return [n]      # n is prime


##############################################################
### factorization of a natural ##################################
##############################################################

def factorGenerator(n):
    p = primefactors(n)
    factors={}
    for p1 in p:
        try:
            factors[p1]+=1
        except KeyError:
            factors[p1]=1
    return factors

def divisors(n):
    factors = factorGenerator(n)
    divisors=[]
    listexponents=[map(lambda x:k**x,range(0,factors[k]+1)) for k in factors.keys()]
    listfactors=cartesianproduct(listexponents)
    for f in listfactors:
        divisors.append(reduce(lambda x, y: x*y, f, 1))
    divisors.sort()
    return divisors



print divisors(60668796879)

P.S. c'est la première fois que je publie sur stackoverflow. Je suis impatient de recevoir vos commentaires.

7
Pietro Speroni

Adapté de CodeReview , voici une variante qui fonctionne avec num=1!

from itertools import product
import operator

def prod(ls):
   return reduce(operator.mul, ls, 1)

def powered(factors, powers):
   return prod(f**p for (f,p) in Zip(factors, powers))


def divisors(num) :

   pf = dict(prime_factors(num))
   primes = pf.keys()
   #For each prime, possible exponents
   exponents = [range(i+1) for i in pf.values()]
   return (powered(primes,es) for es in product(*exponents))
2
YvesgereY

Voici un moyen intelligent et rapide de le faire pour des nombres allant jusqu’à 10 ** 16 environ en pur 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))
1
Bruno Astrolino

Ancienne question, mais voici ma position:

def divs(n, m):
    if m == 1: return [1]
    if n % m == 0: return [m] + divs(n, m - 1)
    return divs(n, m - 1)

Vous pouvez utiliser un proxy avec:

def divisorGenerator(n):
    for x in reversed(divs(n, n)):
        yield x

Remarque: pour les langues qui prennent en charge, cela peut être récursif.

1
joksnet

Je vais juste ajouter une version légèrement révisée d'Anivarth (car je crois que c'est la plus Pythonique) pour référence future.

from math import sqrt

def divisors(n):
    divs = {1,n}
    for i in range(2,int(sqrt(n))+1):
        if n%i == 0:
            divs.update((i,n//i))
    return divs
1
ppw0

Voici ma solution. Cela semble être stupide mais fonctionne bien ... et j'essayais de trouver tous les diviseurs appropriés afin que la boucle commence à i = 2. 

import math as m 

def findfac(n):
    faclist = [1]
    for i in range(2, int(m.sqrt(n) + 2)):
        if n%i == 0:
            if i not in faclist:
                faclist.append(i)
                if n/i not in faclist:
                    faclist.append(n/i)
    return facts
0
Amber Xue

En supposant que la fonction factors renvoie les facteurs de n (par exemple, factors(60) renvoie la liste [2, 2, 3, 5]), voici une fonction permettant de calculer les diviseurs de n :

function divisors(n)
    divs := [1]
    for fact in factors(n)
        temp := []
        for div in divs
            if fact * div not in divs
                append fact * div to temp
        divs := divs + temp
    return divs
0
user448810

Si vous ne vous souciez que d'utiliser des listes de compréhension, rien ne vous importe!

from itertools import combinations
from functools import reduce

def get_devisors(n):
    f = [f for f,e in list(factorGenerator(n)) for i in range(e)]
    fc = [x for l in range(len(f)+1) for x in combinations(f, l)]
    devisors = [1 if c==() else reduce((lambda x, y: x * y), c) for c in set(fc)]
    return sorted(devisors)
0
Sadiq