¿Alguien puede explicarme una forma eficiente de encontrar todos los factores de un número en Python (2.7)?
Puedo crear un algoritmo para hacer esto, pero creo que está mal codificado y lleva demasiado tiempo producir un resultado para un gran número.
primefac
? pypi.python.org/pypi/primefacRespuestas:
Esto devolverá todos los factores, muy rápidamente, de un número
n
.¿Por qué raíz cuadrada como límite superior?
sqrt(x) * sqrt(x) = x
. Entonces, si los dos factores son iguales, ambos son la raíz cuadrada. Si aumenta un factor, debe reducir el otro factor. Esto significa que uno de los dos siempre será menor o igual quesqrt(x)
, por lo que solo tiene que buscar hasta ese punto para encontrar uno de los dos factores coincidentes. Luego puede usarx / fac1
para obtenerfac2
.El
reduce(list.__add__, ...)
está tomando las pequeñas listas[fac1, fac2]
y uniéndolas en una larga lista.Las
[i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0
declaraciones de un par de factores si el resto cuando se dividen
por el más pequeño es cero (no es necesario para comprobar la más grande también, que sólo se pone que al dividirn
por la más pequeña.)En
set(...)
el exterior, se eliminan los duplicados, lo que solo ocurre con los cuadrados perfectos. Paran = 4
, esto volverá2
dos veces, así queset
deshazte de uno de ellos.fuente
sqrt
- probablemente fue antes de que la gente realmente pensara en admitir Python 3. Creo que el sitio donde lo obtuve lo probé__iadd__
y fue más rápido . Sin embargo, parece recordar algo sobrex**0.5
ser más rápido quesqrt(x)
en algún momento, y es más infalible de esa manera.if not n % i
lugar deif n % i == 0
/
devolverá un flotante incluso si ambos argumentos son enteros y son exactamente divisibles, es decir,4 / 2 == 2.0
no2
.from functools import reduce
para que esto funcione.La solución presentada por @agf es excelente, pero se puede lograr un tiempo de ejecución ~ 50% más rápido para un número impar arbitrario al verificar la paridad. Como los factores de un número impar siempre son impares, no es necesario verificarlos cuando se trata de números impares.
Acabo de empezar a resolver los acertijos del Proyecto Euler . En algunos problemas, se llama una comprobación de divisor dentro de dos
for
bucles anidados , y el rendimiento de esta función es, por lo tanto, esencial.Combinando este hecho con la excelente solución de agf, terminé con esta función:
Sin embargo, en números pequeños (~ <100), la sobrecarga adicional de esta alteración puede hacer que la función tarde más.
Realicé algunas pruebas para verificar la velocidad. A continuación se muestra el código utilizado. Para producir las diferentes parcelas, alteré el
X = range(1,100,1)
correspondiente.X = rango (1,100,1)
No hay diferencia significativa aquí, pero con números más grandes, la ventaja es obvia:
X = rango (1,100000,1000) (solo números impares)
X = rango (2,100000,100) (solo números pares)
X = rango (1,100000,1001) (paridad alterna)
fuente
La respuesta de agf es realmente genial. Quería ver si podía reescribirlo para evitar usarlo
reduce()
. Esto es lo que se me ocurrió:También probé una versión que usa funciones generadoras difíciles:
Lo cronometré computando:
Lo ejecuté una vez para dejar que Python lo compilara, luego lo ejecuté bajo el comando time (1) tres veces y mantuve el mejor tiempo.
Tenga en cuenta que la versión de itertools está creando una tupla y pasándola a flatten_iter (). Si cambio el código para crear una lista, se ralentiza ligeramente:
Creo que la versión de funciones del generador complicado es la más rápida posible en Python. Pero no es realmente mucho más rápido que la versión reducida, aproximadamente un 4% más rápido según mis mediciones.
fuente
for tup in
):factors = lambda n: {f for i in range(1, int(n**0.5)+1) if n % i == 0 for f in [i, n//i]}
Un enfoque alternativo a la respuesta de agf:
fuente
reduce()
era significativamente más rápido, así que hice casi todo lo que no fuera lareduce()
parte de la misma manera que agf. Para facilitar la lectura, sería bueno ver una llamada de función como enis_even(n)
lugar de una expresión comon % 2 == 0
.Aquí hay una alternativa a la solución de @ agf que implementa el mismo algoritmo en un estilo más pitónico:
Esta solución funciona en Python 2 y Python 3 sin importar y es mucho más legible. No he probado el rendimiento de este enfoque, pero asintóticamente debería ser el mismo, y si el rendimiento es una preocupación seria, ninguna de las soluciones es óptima.
fuente
Hay un algoritmo de fuerza industrial en SymPy llamado factorint :
Esto tomó menos de un minuto. Cambia entre un cóctel de métodos. Consulte la documentación vinculada anteriormente.
Dados todos los factores primos, todos los demás factores pueden construirse fácilmente.
Tenga en cuenta que incluso si se permitió que la respuesta aceptada se ejecutara durante el tiempo suficiente (es decir, una eternidad) para factorizar el número anterior, para algunos números grandes fallará, como en el siguiente ejemplo. Esto se debe a lo descuidado
int(n**0.5)
. Por ejemplo, cuandon = 10000000000000079**2
tenemosComo 10000000000000079 es primo , el algoritmo de la respuesta aceptada nunca encontrará este factor. Tenga en cuenta que no es solo un off-by-one; para números más grandes se desactivará por más. Por esta razón, es mejor evitar los números de coma flotante en algoritmos de este tipo.
fuente
sympy.divisors
no sea mucho más rápido, para números con pocos divisores en particular. ¿Tienes algún punto de referencia?sympy.divisors
para 100,000 y más lenta para cualquier cosa más alta (cuando la velocidad realmente importa). (Y, por supuesto,sympy.divisors
funciona en números como10000000000000079**2
.)Para n hasta 10 ** 16 (tal vez incluso un poco más), aquí hay una solución rápida y pura de Python 3.6,
fuente
Mejora adicional a la solución de afg & eryksun. El siguiente código devuelve una lista ordenada de todos los factores sin cambiar la complejidad asintótica del tiempo de ejecución:
Idea: en lugar de usar la función list.sort () para obtener una lista ordenada que da complejidad a nlog (n); Es mucho más rápido usar list.reverse () en l2 que requiere complejidad O (n). (Así es como se hace python). Después de l2.reverse (), l2 puede agregarse a l1 para obtener la lista ordenada de factores.
Tenga en cuenta que l1 contiene i -s que están aumentando. l2 contiene q -s que están disminuyendo. Esa es la razón detrás del uso de la idea anterior.
fuente
list.reverse
es O (n) no O (1), no es que cambie la complejidad general.l1 + l2.reversed()
lugar de invertir la lista en su lugar.He intentado la mayoría de estas maravillosas respuestas con tiempo para comparar su eficiencia con mi función simple y, sin embargo, veo constantemente que la mía supera a las que se enumeran aquí. Pensé en compartirlo y ver qué piensan ustedes.
Como está escrito, tendrá que importar matemáticas para probar, pero reemplazar math.sqrt (n) con n **. 5 debería funcionar igual de bien. No me molesto en perder el tiempo buscando duplicados porque los duplicados no pueden existir en un conjunto independientemente.
fuente
xrange(1, int(math.sqrt(n)) + 1)
se evalúa una vezAquí hay otra alternativa sin reducción que funciona bien con grandes números. Se utiliza
sum
para aplanar la lista.fuente
sum
nireduce(list.__add__)
para aplanar una lista.Asegúrese de obtener el número más grande que
sqrt(number_to_factor)
para números inusuales como 99 que tiene 3 * 3 * 11 yfloor sqrt(99)+1 == 10
.fuente
x=8
esperado:,[1, 2, 4, 8]
obtenido:[2, 2, 2]
La forma más simple de encontrar factores de un número:
fuente
Aquí hay un ejemplo si desea utilizar el número primos para ir mucho más rápido. Estas listas son fáciles de encontrar en Internet. Agregué comentarios en el código.
fuente
un algoritmo potencialmente más eficiente que los presentados aquí ya (especialmente si hay pequeños factores primos en
n
). El truco aquí es ajustar el límite hasta el cual se necesita la división de prueba cada vez que se encuentran factores primos:Esto, por supuesto, sigue siendo una división de prueba y nada más elegante. y, por lo tanto, sigue siendo muy limitada en su eficiencia (especialmente para grandes números sin pequeños divisores).
esto es python3; la división
//
debería ser lo único que necesita adaptar para python 2 (agregarfrom __future__ import division
).fuente
El uso
set(...)
hace que el código sea un poco más lento, y solo es realmente necesario cuando se verifica la raíz cuadrada. Aquí está mi versión:La
if sq*sq != num:
condición es necesaria para números como 12, donde la raíz cuadrada no es un número entero, pero el piso de la raíz cuadrada es un factor.Tenga en cuenta que esta versión no devuelve el número en sí, pero eso es una solución fácil si lo desea. La salida tampoco está ordenada.
Lo cronometré corriendo 10000 veces en todos los números 1-200 y 100 veces en todos los números 1-5000. Supera a todas las otras versiones que probé, incluidas las soluciones de dansalmo, Jason Schorn, oxrock, agf, steveha y eryksun, aunque la de oxrock es, con mucho, la más cercana.
fuente
su factor máximo no es más que su número, entonces, digamos
voilá!
fuente
fuente
Use algo tan simple como la siguiente lista de comprensión, y observe que no necesitamos probar 1 y el número que estamos tratando de encontrar:
En referencia al uso de la raíz cuadrada, digamos que queremos encontrar factores de 10. La porción entera del
sqrt(10) = 4
por lo tantorange(1, int(sqrt(10))) = [1, 2, 3, 4]
y probar hasta 4 claramente falla 5.A menos que me falte algo que sugeriría, si debe hacerlo de esta manera, usando
int(ceil(sqrt(x)))
. Por supuesto, esto produce muchas llamadas innecesarias a las funciones.fuente
Creo que la solución de legibilidad y velocidad de @ oxrock es la mejor, así que aquí está el código reescrito para python 3+:
fuente
Me sorprendió bastante cuando vi esta pregunta que nadie usaba numpy incluso cuando numpy es mucho más rápido que los bucles de pitón. Al implementar la solución de @ agf con numpy y resultó en promedio 8 veces más rápido . Creo que si implementaras algunas de las otras soluciones en numpy, podrías obtener momentos increíbles.
Aquí está mi función:
Observe que los números del eje x no son la entrada a las funciones. La entrada a las funciones es 2 al número en el eje x menos 1. Entonces, donde diez es la entrada sería 2 ** 10-1 = 1023
fuente
fuente
Creo que esta es la forma más sencilla de hacerlo:
fuente