liste diviseurs nombre python

Liste des diviseurs d’un nombre avec Python

  • Dernière modification de la publication :2 avril 2024
  • Temps de lecture :8 min de lecture
  • Commentaires de la publication :0 commentaire

Loading

Obtenir la liste de tous les diviseurs d’un nombre en Python peut se faire de plusieurs façons. Nous allons en voir quelques unes ici.

Obtenir la liste des diviseurs d’un nombre avec Python: la manière brutale

C’est la manière la plus répandue et aussi la plus simple, il suffit de faire une boucle de 1 jusqu’au nombre et de tester la divisibilité de chacun des nombres rencontrés.

def liste_diviseurs( n ):
    L = [1]
    for i in range(2,n+1):
        if n%i == 0:
            L.append(i)
            
    return L
>>> liste_diviseurs( 198 )
[1, 2, 3, 6, 9, 11, 18, 22, 33, 66, 99, 198]

C’est vrai que c’est simple et rapide! Enfin… Essayez avec ceci:

>>> liste_diviseurs( 1985856585 )

Vous allez vous en mordre les doigts… 🙂

La complexité de cette fonction est en O(n). Autant dire que plus le nombre n est grand, plus long sera le temps d’exécution.

Obtenir la liste des diviseurs d’un nombre avec Python à l’aide de la décomposition en produit de facteurs premiers

Une deuxième approche est d’utiliser la décomposition en produit de facteurs premiers du nombre. Alors, certes, l’implémentation est moins directe que la précédente, mais elle a son avantage…

J’ai déjà parlé dans cet article d’une manière d’obtenir la décomposition en produit de facteurs premiers.

from itertools import combinations

def decomp(n):
    D = dict() # dictionnaire vide
    k = 2
    while n > 1:
        exposant = 0
        while n%k == 0:
            exposant += 1
            n /= k
        if exposant != 0:
            D[k] = exposant
        k = k + 1
        
    return D

def liste_diviseurs(nombre):
    D = decomp(nombre)
    L = ['1']
    for key,value in D.items():
        for _ in range(value):
            L.append(str(key))
            
    A = [ [] for _ in range(len(L)-1) ]
    
    for i in range( 2 , len(L)+1 ):
        for j in combinations(L,i):
            if j not in A[i-2]:
                A[i-2].append( j )
        
    diviseurs_list = [1]
    for line in A:
        for c in line:
            p = 1
            for n in c:
                p *= int(n)
            if p not in diviseurs_list:
                diviseurs_list.append(p)
    
    return sorted(diviseurs_list)

L’avantage de cette façon de faire est que l’on réduit considérablement le nombres d’opérations.

Quand le programme précédent met énormément de temps à afficher le résultat (d’ailleurs, sur mon ordinateur Processeur Intel 12th Gen. Core i7-2.3 Ghz, 16 Go RAM), après 20 secondes, il n’y a rien qui s’affiche), ce dernier met 11 secondes. C’est mieux, mais pas ouf non plus…

Nous allons voir qu’il existe une manière bien plus rapide!

Accélération

Il faut accélérer la manière dont on décompose en produit de facteurs premiers le nombre. Je me suis donc mis à la recherche d’un script performant et je suis tombé sur la page https://www.codingame.com/playgrounds/53943/decomposition-en-facteurs-premiers-. Cela m’a permis d’améliorer le code précédent pour le suivant:

from time import time
from random import randint
from itertools import combinations

# Origine du code suivant: https://www.codingame.com/playgrounds/53943/decomposition-en-facteurs-premiers-

def gcd(a,b):
    # Calcule le PGCD de a et b
    while b>0:
        a,b=b,a%b      
    return a

def millerTest(a,d,n,r):
    # test de Miller pour un témoin a
    # Retourne faux si n est composé et vrai si n est probablement premier
    # d et r doivent vérifier n = 2^r * d + 1 avec d impair   
           
    x = pow(a, d, n) # Calcule a^d % n   
    if (x == 1  or x == n-1): 
       return True

    for _ in range(r):    
        x = (x * x) % n 
        if (x == 1):
            return False 
        if (x == n-1):
            return True    
   
    return False 

def isPrime(n, k=25): 
    # Test de primalité de Miller Rabin  
    # Si faux alors n est composé et si vrai alors n est probablement premier 
    # k determine le niveau de certitude : P(erreur) < 1/4**k
    
    if (n <= 1 or n == 4):
        return False 
    if (n <= 5):
        return True   
    
    # Trouver d et r tels que n = 2^r * d + 1 avec d impair 
    d = n - 1 
    r = 0
    while (d&1 == 0): 
        d  >>= 1 
        r += 1 
    
    # Effectuer k tests de Miller
    for i in range(k):
        a = randint(2,n-2) 
        if (not millerTest(a, d, n, r)):
              return False  
    return True

def nextPrime(n):
    # premier suivant n
    while not isPrime(n):
        n += 1
    return n

def pollard(n,a=1,c=3):
    # Recherche un diviseur de n
    # Cet algorithme est un mélange des méthodes rho et p-1 de Pollard
    # Echec,si la valeur retournée est n 
    if n&1==0: #pair
        return 2
    b,g=a,1   
    while g==1:
        a = (a*a+1)%n
        b = (b*b+1)%n
        b = (b*b+1)%n
        c = pow(c,a,n)
        g = gcd(n, ((c-1)*abs(b-a))%n)
    
    #print('Polard rho et p-1 : iter', i, 'n=', n,'g=', g)
    return g

def rho(n,a=2):
    # Recherche un diviseur de n
    # Cet algorithme est une variante de la méthodes rho de Pollard
    # Echec,si la valeur retournée est n   
    x=a  
    g=k=p=1
    i=0    
    while g==1:
        a = (a*a+1)%n
        f = abs(a-x)
        if f==0 or i%k==0:
            g = gcd(n,p)
            if f==0:
                break
            k<<=1
            x = a
            p = 1
               
        i += 1
        p *= f
        p %= n
        if p==0 and g==1:
            g = gcd(f,n)
                
    #print('Rho : iter', i, 'n=', n, 'g=', g)
    return g

def factor(n, k=25, div=rho):
    # Décompose n en facteurs probablement premiers
    # div = pollard ou rho
    comp = pow(2,n,n)!=2 # n est composé si vrai
    if n==2 or (n<341 and not comp): # n est un premier < 341 
            return [n]
    if not comp and isPrime(n,k): # n est probablement premier
        return [n]
    if n&1==0: # n est pair
        return [2]+factor(n>>1)
    for p in [3,5,7,11,13,17,19]:
        if n%p==0: #n est divisible par un petit premier
            return [p]+factor(n//p)
    f=div(n) # f est un facteur de n   
    while f==1 or f==n: # tant qu'on n'a pas de facteur strict
        # on essaye avec d'autres valeurs
        f=div(n,a=randint(0,n-1))                   
    return factor(f)+factor(n//f)

# fin du code

def liste_diviseurs(nombre):
    f = factor(nombre)
    while True:
        for n in f:
            if isPrime(n):
                L = ['1']+factor(nombre)
                        
                A = [ [] for _ in range(len(L)-1) ]
                
                for i in range( 2 , len(L)+1 ):
                    for j in combinations(L,i):
                        if j not in A[i-2]:
                            A[i-2].append( j )
                    
                diviseurs_list = [1]
                for line in A:
                    for c in line:
                        p = 1
                        for n in c:
                            p *= int(n)
                        if p not in diviseurs_list:
                            diviseurs_list.append(p)
                
                return sorted(diviseurs_list)

a=24769492052181606629
t=time()
        
print( liste_diviseurs(a) )

t=time()-t
print('temps :',t) 
[1, 310547, 847193, 94147199, 263093244571, 29237130207853, 79760847962407, 24769492052181606629]
temps : 0.00298309326171875

Voyez la différence de temps d’exécution!

Nous devons donc retenir de ceci le fait suivant: un programme court n’est pas toujours le meilleur programme, ni même le plus performant, loin de là…

0 0 votes
Évaluation de l'article
S’abonner
Notification pour
guest
0 Commentaires
Commentaires en ligne
Afficher tous les commentaires