¿Cuál es la mejor forma de obtener todos los divisores de un número?

105

Esta es la forma más tonta:

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

El resultado que me gustaría obtener es similar a este, pero me gustaría un algoritmo más inteligente (este es demasiado lento y tonto :-)

Puedo encontrar factores primos y su multiplicidad lo suficientemente rápido. Tengo un generador que genera factor de esta manera:

(factor1, multiplicidad1)
(factor2, multiplicidad2)
(factor3, multiplicidad3)
y así sucesivamente ...

es decir, la salida de

for i in factorGenerator(100):
    print i

es:

(2, 2)
(5, 2)

No sé cuánto es esto útil para lo que quiero hacer (lo codifiqué para otros problemas), de todos modos me gustaría una forma más inteligente de hacer

for i in divisorGen(100):
    print i

salida esto:

1
2
4
5
10
20
25
50
100

ACTUALIZACIÓN: Muchas gracias a Greg Hewgill y su "manera inteligente" :) Calcular todos los divisores de 100000000 tomó 0.01s con su camino contra los 39s que el modo tonto tomó en mi máquina, muy bueno: D

ACTUALIZACIÓN 2: Deja de decir que esto es un duplicado de esta publicación. Calcular el número de divisores de un número dado no necesita calcular todos los divisores. Es un problema diferente, si cree que no lo es, busque "Función divisor" en wikipedia. Lea las preguntas y la respuesta antes de publicar, si no entiende cuál es el tema, simplemente no agregue respuestas que no sean útiles y que ya hayan dado.

Andrea Ambu
fuente
La razón por la que se sugirió que esta pregunta era casi un duplicado del "Algoritmo para calcular el número de divisores de un número dado" fue que el primer paso sugerido en esa pregunta era encontrar todos los divisores , que creo que es exactamente que estabas tratando de hacer
Andrew Edgecombe
4
Andrew para encontrar cuántos divisores hay, simplemente tienes que encontrar los factores primos y luego usarlos para contar cuántos divisores puede haber. En ese caso, no es necesario encontrar divisores.
Loïc Faure-Lacroix
1
@Andrea Ambu, corrija los nombres de sus funciones
minerales

Respuestas:

77

Dada su factorGeneratorfunción, aquí hay una divisorGenque debería funcionar:

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

La eficiencia general de este algoritmo dependerá completamente de la eficiencia del factorGenerator.

Greg Hewgill
fuente
2
wow, se necesitaron 0.01 para calcular todos los divisores de 100000000 contra los 39 que tomaron el camino tonto (deteniéndose en n / 2) muy bueno, ¡gracias!
Andrea Ambu
47
Para aquellos de nosotros que no entendemos Pythonese, ¿qué está haciendo esto realmente?
Matthew Scharley
1
monóxido: calcula todas las combinaciones multiplicativas de los factores dados. La mayor parte debería ser autoexplicativa; la línea de "rendimiento" es como un retorno pero continúa después de devolver un valor. [0] * nfactors crea una lista de ceros de longitud nfactors. reduce (...) calcula el producto de los factores.
Greg Hewgill
La notación reduce y lambda eran las partes que realmente me confundían. Intenté implementar un algoritmo para hacer esto en C # usando una función recursiva para recorrer una matriz de factores y multiplicarlos todos juntos, pero parece tener un rendimiento horrible en números como 1024 que tienen muchos factores
Matthew Scharley
3
Esto, por supuesto, es mucho mejor que dividir por cada número hasta n / 2 o incluso sqrt (n), pero esta implementación en particular tiene dos inconvenientes: bastante ineficaz: toneladas de multiplicación y exponenciación, multiplicar repetidamente los mismos poderes, etc. Parece Pythonic, pero no creo que Python se trate de matar el rendimiento. Problema dos: los divisores no se devuelven en orden.
Tomasz Gandor
34

Para ampliar lo que ha dicho Shimi, solo debe ejecutar su ciclo desde 1 hasta la raíz cuadrada de n. Luego, para encontrar el par, hazlo n / i, y esto cubrirá todo el espacio del problema.

Como también se señaló, este es un problema NP o "difícil". La búsqueda exhaustiva, la forma en que la está haciendo, es tan buena como puede obtener respuestas garantizadas. Este hecho es utilizado por algoritmos de cifrado y similares para ayudar a protegerlos. Si alguien resolviera este problema, la mayor parte, si no toda, de nuestra comunicación "segura" actual se volvería insegura.

Código 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))

Que debería generar una lista como:

[1, 2, 4, 5, 10, 20, 25, 50, 100]
Matthew Scharley
fuente
2
Porque, una vez que tenga una lista de elementos entre 1..10, puede generar cualquiera de los elementos entre 11..100 triviamente. Obtienes {1, 2, 4, 5, 10}. Divida 100 por cada uno de estos elementos y obtendrá {100, 50, 20, 25, 10}.
Matthew Scharley
2
Los factores SIEMPRE se generan en pares, en virtud de la definición. Al buscar solo en sqrt (n), estás reduciendo tu trabajo en un poder 2.
Matthew Scharley
Es mucho más rápido que la versión en mi publicación, pero aún es demasiado lento que la versión que usa factores primos
Andrea Ambu
Estoy de acuerdo en que esta no es la mejor solución. Simplemente estaba señalando una forma "mejor" de hacer la búsqueda "tonta" que ya ahorraría mucho tiempo.
Matthew Scharley
No se ha demostrado que la factorización sea NP-difícil. en.wikipedia.org/wiki/Integer_factorization Y el problema era encontrar todos los divisores dado que los factores primos (la parte difícil) ya se habían encontrado.
Jamie
19

Aunque ya hay muchas soluciones para esto, realmente tengo que publicar esto :)

Este es:

  • legible
  • corto
  • autónomo, listo para copiar y pegar
  • rápido (en casos con muchos factores primos y divisores,> 10 veces más rápido que la solución aceptada)
  • compatible con python3, python2 y pypy

Código:

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
Tomas Kulich
fuente
Reemplazaría while i*i <= nnpor while i <= limit, dondelimit = math.sqrt(n)
Rafa0809
17

Creo que puedes detenerte en math.sqrt(n) lugar de n / 2.

Te daré un ejemplo para que lo entiendas fácilmente. Ahora el sqrt(28)es 5.29tanceil(5.29) será 6. Entonces, si me detengo en 6, entonces puedo obtener todos los divisores. ¿Cómo?

Primero vea el código y luego vea la imagen:

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

Ahora, vea la imagen a continuación:

Digamos que ya he añadido 1a mi lista de divisores y comienzo con i=2por lo

Divisores de un 28

Entonces, al final de todas las iteraciones, ya que agregué el cociente y el divisor a mi lista, se completan todos los divisores de 28.

Fuente: Cómo determinar los divisores de un número.

Anivarth
fuente
2
¡¡Bien bien!! math.sqrt(n) instead of n/2es obligatorio para la elegancia
Rafa0809
Esto es incorrecto. Olvidaste que n es divisible por sí mismo.
Jasonleonhard
1
Buena respuesta. Sencillo y claro. Pero para python 3 hay 2 cambios necesarios: n / i debe escribirse usando int (n / i) porque n / i produce un número flotante. Además, rangex está obsoleto en Python 3 y ha sido reemplazado por range.
Geoffroy CALA
7

Me gusta la solución de Greg, pero desearía que fuera más como una pitón. Siento que sería más rápido y más legible; así que después de un tiempo de codificación salí con esto.

Las dos primeras funciones son necesarias para hacer el producto cartesiano de listas. Y se puede reutilizar siempre que surja este problema. Por cierto, tuve que programarlo yo mismo, si alguien conoce una solución estándar para este problema, no dude en ponerse en contacto conmigo.

"Factorgenerator" ahora devuelve un diccionario. Y luego el diccionario se alimenta a "divisores", quienes lo usan para generar primero una lista de listas, donde cada lista es la lista de los factores de la forma p ^ n con p primo. Luego hacemos el producto cartesiano de esas listas y finalmente usamos la solución de Greg para generar el divisor. Los clasificamos y los devolvemos.

Lo probé y parece ser un poco más rápido que la versión anterior. Lo probé como parte de un programa más grande, así que realmente no puedo decir cuánto es más rápido.

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)

PD: es la primera vez que publico en stackoverflow. Espero cualquier comentario.

Pietro Speroni
fuente
En Python 2.6 hay un itertools.product ().
jfs
Una versión que use generadores en lugar de list.append en todas partes podría ser más limpia.
jfs
Sieve of Eratosthenes podría usarse para generar números primos menores o iguales sqrt (n) stackoverflow.com/questions/188425/project-euler-problem#193605
jfs
1
Estilo de codificación: exponentes = [k ** x para k, v en factores.items () para x en el rango (v + 1)]
jfs
Para listexponents: [[k ** x para x en rango (v + 1)] para k, v en factores.items ()]
klenwell
3

Aquí hay una forma inteligente y rápida de hacerlo para números de hasta 10 ** 16 en Python 3.6 puro,

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))
Bruno Astrolino
fuente
¿Cuál es el nombre del algoritmo utilizado para encontrar los números primos y factorizar? Porque quiero implementar esto en C # ..
Kyu96
2

Adaptado de CodeReview , aquí hay una variante que funciona con 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))
YvesgereY
fuente
1
Parece que estoy recibiendo un error: NameError: global name 'prime_factors' is not defined. Ninguna de las otras respuestas, ni la pregunta original, define qué hace esto.
AnnanFay
2

Solo voy a agregar una versión ligeramente revisada de Anivarth (ya que creo que es la más pitónica) para referencia futura.

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
ppw0
fuente
1

Antigua pregunta, pero aquí está mi opinión:

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

Puede proxy con:

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

NOTA: Para los idiomas que lo admiten, esto podría ser una cola recursiva.

joksnet
fuente
0

Suponiendo que la factorsfunción devuelve los factores de n (por ejemplo, factors(60)devuelve la lista [2, 2, 3, 5]), aquí hay una función para calcular los divisores 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
usuario448810
fuente
¿Eso es pitón? De todos modos, no es Python 3.x seguro.
GinKin
Es un pseudocódigo, que debería ser sencillo de traducir a Python.
user448810
3 años tarde, mejor tarde que nunca :) En mi opinión, este es el código más simple y corto para hacer esto. No tengo una tabla de comparación, pero puedo factorizar y calcular divisores de hasta un millón en 1 en mi computadora portátil i5.
Riyaz Mansoor
0

Esta es mi solución. Parece ser tonto pero funciona bien ... y estaba tratando de encontrar todos los divisores adecuados para que el ciclo comenzara desde 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
Ámbar xue
fuente
error tipográfico: return hechos => return faclist
Jonath P
0

¡Si solo le importa usar listas por comprensión y nada más le importa!

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)
Sadiq
fuente
0

Si su PC tiene toneladas de memoria, una sola línea bruta puede ser lo suficientemente rápida 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])

Toma menos de 1 segundo en mi PC lenta.

Mathieu Villion
fuente
0

Mi solución a través de la función del generador es:

def divisor(num):
    for x in range(1, num + 1):
        if num % x == 0:
            yield x
    while True:
        yield None
Eugenio
fuente
-1
return [x for x in range(n+1) if n/x==int(n/x)]
usuario3697453
fuente
3
El interrogador pidió un algoritmo mejor, no solo un formato más bonito.
Veedrac
4
Necesita usar range (1, n + 1) para evitar la división por cero. Además, debe usar float (n) para la primera división si usa Python 2.7, aquí 1/2 = 0
Jens Munk
-1

Para mí, esto funciona bien y también está limpio (Python 3)

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

No es muy rápido, pero devuelve divisores línea por línea como desee, también puede hacer list.append (n) y list.append (number) si realmente lo desea

tomekch6
fuente