Qual è il modo migliore per ottenere tutti i divisori di un numero?


108

Ecco il modo molto stupido:

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

Il risultato che vorrei ottenere è simile a questo, ma vorrei un algoritmo più intelligente (questo è troppo lento e stupido :-)

Riesco a trovare i fattori primi e la loro molteplicità abbastanza velocemente. Ho un generatore che genera factor in questo modo:

(fattore1, molteplicità1)
(fattore2, molteplicità2)
(fattore3, molteplicità3)
e così via ...

cioè l'output di

for i in factorGenerator(100):
    print i

è:

(2, 2)
(5, 2)

Non so quanto sia utile per quello che voglio fare (l'ho codificato per altri problemi), comunque vorrei un modo più intelligente per fare

for i in divisorGen(100):
    print i

output questo:

1
2
4
5
10
20
25
50
100

AGGIORNAMENTO: Mille grazie a Greg Hewgill e al suo "modo intelligente" :) Calcolare tutti i divisori di 100000000 ha impiegato 0,01 secondi contro i 39 che il modo stupido ha preso sulla mia macchina, molto bello: D

AGGIORNAMENTO 2: Smettila di dire che questo è un duplicato di questo post. Calcolare il numero di divisori di un dato numero non ha bisogno di calcolare tutti i divisori. È un problema diverso, se pensi che non lo sia, cerca "Funzione Divisor" su wikipedia. Leggi le domande e la risposta prima di postare, se non capisci qual è l'argomento non aggiungere risposte non utili e già date.


Il motivo per cui è stato suggerito che questa domanda fosse quasi un duplicato dell '"Algoritmo per calcolare il numero di divisori di un dato numero" era che il primo passo suggerito in quella domanda era trovare tutti i divisori , che credo sia esattamente cosa stavi cercando di fare?
Andrew Edgecombe,

4
Andrew per trovare quanti divisori ci sono devi semplicemente trovare i fattori primi e poi usarli per contare quanti divisori potrebbero esserci. In questo caso non è necessario trovare divisori.
Loïc Faure-Lacroix

1
@Andrea Ambu, correggi i nomi delle funzioni
minerali

Risposte:


77

Data la tua factorGeneratorfunzione, ecco una divisorGenche dovrebbe funzionare:

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'efficienza complessiva di questo algoritmo dipenderà interamente dall'efficienza del factorGenerator.


2
wow ci sono voluti 0,01 per calcolare tutti i divisori di 100000000 contro i 39 che hanno preso la strada stupida (fermandosi a n / 2) molto bello, grazie!
Andrea Ambu

47
Per quelli di noi che non capiscono Pythonese, cosa sta facendo in realtà?
Matthew Scharley,

1
monossido: calcola tutte le combinazioni moltiplicative dei fattori dati. La maggior parte dovrebbe essere autoesplicativa; la linea "rendimento" è come un ritorno ma continua dopo aver restituito un valore. [0] * nfactors crea un elenco di zeri di lunghezza nfactors. reduce (...) calcola il prodotto dei fattori.
Greg Hewgill,

La notazione reduce e lambda erano le parti che in realtà mi confondevano. Ho provato a implementare un algoritmo per farlo in C # utilizzando una funzione ricorsiva per percorrere una serie di fattori e moltiplicarli tutti insieme, ma sembra avere prestazioni orribili su numeri come 1024 che hanno molti fattori
Matthew Scharley

3
Questo è ovviamente notevolmente migliore della divisione per ogni numero fino a n / 2 o anche sqrt (n), ma questa particolare implementazione ha due inconvenienti: abbastanza inefficace: tonnellate di moltiplicazione ed esponenziazione, moltiplicando ripetutamente le stesse potenze ecc. Sembra Pythonic, ma non credo che Python stia uccidendo le prestazioni. Problema due: i divisori non vengono restituiti in ordine.
Tomasz Gandor

34

Per espandere ciò che ha detto Shimi, dovresti solo eseguire il tuo ciclo da 1 alla radice quadrata di n. Quindi per trovare la coppia, fallo n / ie questo coprirà l'intero spazio del problema.

Come è stato anche notato, questo è un problema NP, o "difficile". La ricerca esaustiva, il modo in cui la stai facendo, è buona quanto si ottiene per le risposte garantite. Questo fatto viene utilizzato dagli algoritmi di crittografia e simili per proteggerli. Se qualcuno risolvesse questo problema, la maggior parte se non tutta la nostra attuale comunicazione "sicura" sarebbe resa insicura.

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

Che dovrebbe produrre un elenco come:

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

2
Perché, una volta che hai un elenco di elementi tra 1..10, puoi generare qualsiasi elemento tra 11..100 in modo banale. Ottieni {1, 2, 4, 5, 10}. Dividi 100 per ciascuno di questi elementi e tu {100, 50, 20, 25, 10}.
Matthew Scharley,

2
I fattori sono SEMPRE generati a coppie, in virtù della definizione. Cercando solo in sqrt (n), stai tagliando il tuo lavoro di una potenza 2.
Matthew Scharley,

È molto più veloce della versione nel mio post, ma è ancora troppo lenta della versione che utilizza i fattori primi
Andrea Ambu

Sono d'accordo che questa non è la soluzione migliore. Stavo semplicemente indicando un modo "migliore" di fare la ricerca "stupida" che avrebbe già fatto risparmiare un sacco di tempo.
Matthew Scharley,

La fattorizzazione non ha dimostrato di essere NP-hard. en.wikipedia.org/wiki/Integer_factorization E il problema era trovare tutti i divisori dato che i fattori primi (la parte difficile) erano già stati trovati.
Jamie,

19

Sebbene ci siano già molte soluzioni a questo, devo davvero postare questo :)

Questo è:

  • leggibile
  • corto
  • autonomo, copia e incolla pronto
  • veloce (nei casi con molti fattori primi e divisori,> 10 volte più veloce della soluzione accettata)
  • compatibile con python3, python2 e pypy

Codice:

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

Vorrei sostituire while i*i <= nnda while i <= limit, dovelimit = math.sqrt(n)
Rafa0809

17

Penso che puoi fermarti a math.sqrt(n)invece di n / 2.

Ti darò un esempio in modo che tu possa capirlo facilmente. Ora sqrt(28)è 5.29così ceil(5.29)sarà 6. Quindi se mi fermo a 6 allora posso ottenere tutti i divisori. Come?

Prima guarda il codice e poi guarda l'immagine:

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

Ora, guarda l'immagine qui sotto:

Diciamo che ho già aggiunto 1alla mia lista dei divisori e comincio con i=2così

Divisori di un 28

Quindi alla fine di tutte le iterazioni poiché ho aggiunto il quoziente e il divisore alla mia lista, tutti i divisori di 28 sono popolati.

Fonte: come determinare i divisori di un numero


2
Bello bello!! math.sqrt(n) instead of n/2è obbligatorio per l'eleganza
Rafa0809

Questo non è corretto. Hai dimenticato che n è divisibile da solo.
jasonleonhard

1
Bella risposta. Semplice e chiaro. Ma per python 3 ci sono 2 modifiche necessarie: n / i dovrebbe essere digitato usando int (n / i) perché n / i produce un numero float. Anche rangex è deprecato in python 3 ed è stato sostituito da range.
Geoffroy CALA

7

Mi piace la soluzione di Greg, ma vorrei che fosse più simile a Python. Sento che sarebbe più veloce e più leggibile; quindi dopo un po 'di tempo di programmazione sono uscito con questo.

Le prime due funzioni sono necessarie per realizzare il prodotto cartesiano delle liste. E può essere riutilizzato ovunque si presenti questo problema. A proposito, ho dovuto programmarlo da solo, se qualcuno conosce una soluzione standard per questo problema, non esitate a contattarmi.

"Factorgenerator" ora restituisce un dizionario. E poi il dizionario viene inserito in "divisori", che lo usano per generare prima una lista di liste, dove ogni lista è la lista dei fattori della forma p ^ n con p primo. Quindi creiamo il prodotto cartesiano di quelle liste e infine usiamo la soluzione di Greg per generare il divisore. Li selezioniamo e li restituiamo.

L'ho provato e sembra essere un po 'più veloce della versione precedente. L'ho testato come parte di un programma più grande, quindi non posso davvero dire quanto sia più veloce.

Pietro Speroni (pietrosperoni dot it)

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)

PS è la prima volta che inserisco in stackoverflow. Non vedo l'ora per qualsiasi feedback.


In Python 2.6 c'è un itertools.product ().
jfs

Una versione che utilizza generatori invece di list.append ovunque potrebbe essere più pulita.
jfs

Il setaccio di Eratostene potrebbe essere utilizzato per generare numeri primi inferiori o uguali a sqrt (n) stackoverflow.com/questions/188425/project-euler-problem#193605
jfs

1
Stile di codifica: esponenti = [k ** x per k, v in factor.items () per x nell'intervallo (v + 1)]
jfs

Per listexponents: [[k ** x for x in range (v + 1)] for k, v inactors.items ()]
klenwell

3

Ecco un modo intelligente e veloce per farlo per numeri fino a 10 ** 16 in puro 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))

Qual è il nome dell'algoritmo utilizzato per trovare i numeri primi e fattorizzare? Perché voglio implementarlo in C # ..
Kyu96

2

Adattato da CodeReview , ecco una variante con cui funziona 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))

1
Mi sembra di essere sempre un errore: NameError: global name 'prime_factors' is not defined. Nessuna delle altre risposte, né la domanda originale, definisce cosa fa.
AnnanFay

2

Aggiungerò solo una versione leggermente rivista di Anivarth (poiché credo sia la più pitonica) per riferimento futuro.

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

Vecchia domanda, ma ecco la mia opinione:

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

Puoi eseguire il proxy con:

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

NOTA: per le lingue che supportano, questo potrebbe essere ricorsivo di coda.


0

Supponendo che la factorsfunzione restituisca i fattori di n (ad esempio, factors(60)restituisce la lista [2, 2, 3, 5]), ecco una funzione per calcolare i divisori di 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

Quello è pitone? Ad ogni modo, di sicuro non è python 3.x.
GinKin

È uno pseudocodice, che dovrebbe essere semplice da tradurre in Python.
user448810

3 anni di ritardo, meglio tardi che mai :) IMO, questo è il codice più semplice e più breve per farlo. Non ho una tabella di confronto, ma posso fattorizzare e calcolare divisori fino a un milione in 1 sul mio laptop portatile i5.
Riyaz Mansoor

0

Ecco la mia soluzione. Sembra essere stupido ma funziona bene ... e stavo cercando di trovare tutti i divisori appropriati, quindi il ciclo è iniziato da 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

errore di battitura: return fact => return faclist
Jonath P

0

Se ti interessa solo usare le comprensioni di elenchi e nient'altro ti interessa!

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

Se il tuo PC ha tonnellate di memoria, una singola riga bruta può essere abbastanza veloce con numpy:

N = 10000000; tst = np.arange(1, N); tst[np.mod(N, tst) == 0]
Out: 
array([      1,       2,       4,       5,       8,      10,      16,
            20,      25,      32,      40,      50,      64,      80,
           100,     125,     128,     160,     200,     250,     320,
           400,     500,     625,     640,     800,    1000,    1250,
          1600,    2000,    2500,    3125,    3200,    4000,    5000,
          6250,    8000,   10000,   12500,   15625,   16000,   20000,
         25000,   31250,   40000,   50000,   62500,   78125,   80000,
        100000,  125000,  156250,  200000,  250000,  312500,  400000,
        500000,  625000, 1000000, 1250000, 2000000, 2500000, 5000000])

Richiede meno di 1 secondo sul mio PC lento.


0

La mia soluzione tramite la funzione generatore è:

def divisor(num):
    for x in range(1, num + 1):
        if num % x == 0:
            yield x
    while True:
        yield None

-1
return [x for x in range(n+1) if n/x==int(n/x)]

3
L'interrogante ha chiesto un algoritmo migliore, non solo un formato più carino.
Veedrac

4
È necessario utilizzare intervallo (1, n + 1) per evitare la divisione per zero. Inoltre, è necessario utilizzare float (n) per la prima divisione se si utilizza Python 2.7, qui 1/2 = 0
Jens Munk

-1

Per me funziona bene ed è anche pulito (Python 3)

def divisors(number):
    n = 1
    while(n<number):
        if(number%n==0):
            print(n)
        else:
            pass
        n += 1
    print(number)

Non molto veloce ma restituisce i divisori riga per riga come vuoi, inoltre puoi fare list.append (n) e list.append (numero) se vuoi davvero

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.