que - ¿Cuál es la forma más eficiente de encontrar todos los factores de un número en Python?
numeros primos python while (19)
Aquí hay otra alternativa sin reducir que funciona bien con números grandes. Utiliza sum
para aplanar la lista.
def factors(n):
return set(sum([[i, n//i] for i in xrange(1, int(n**0.5)+1) if not n%i], []))
¿Puede alguien explicarme una forma eficiente de encontrar todos los factores de un número en Python (2.7)?
Puedo crear algoritmos para hacer este trabajo, pero creo que está mal codificado y lleva demasiado tiempo ejecutar un resultado para un gran número.
Aquí hay un ejemplo si desea usar el número de primos para ir mucho más rápido. Estas listas son fáciles de encontrar en internet. Agregué comentarios en el código.
# http://primes.utm.edu/lists/small/10000.txt
# First 10000 primes
_PRIMES = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013,
# Mising a lot of primes for the purpose of the example
)
from bisect import bisect_left as _bisect_left
from math import sqrt as _sqrt
def get_factors(n):
assert isinstance(n, int), "n must be an integer."
assert n > 0, "n must be greather than zero."
limit = pow(_PRIMES[-1], 2)
assert n <= limit, "n is greather then the limit of {0}".format(limit)
result = set((1, n))
root = int(_sqrt(n))
primes = [t for t in get_primes_smaller_than(root + 1) if not n % t]
result.update(primes) # Add all the primes factors less or equal to root square
for t in primes:
result.update(get_factors(n/t)) # Add all the factors associted for the primes by using the same process
return sorted(result)
def get_primes_smaller_than(n):
return _PRIMES[:_bisect_left(_PRIMES, n)]
Aquí hay una alternativa a la solución de @ agf que implementa el mismo algoritmo en un estilo más pythonic:
def factors(n):
return set(
factor for i in range(1, int(n**0.5) + 1) if n % i == 0
for factor in (i, n//i)
)
Esta solución funciona tanto en Python 2 como en 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 solución es óptima.
Asegúrese de tomar el número más grande que sqrt(number_to_factor)
para números inusuales como 99 que tiene 3 * 3 * 11 y floor sqrt(99)+1 == 10
.
import math
def factor(x):
if x == 0 or x == 1:
return None
res = []
for i in range(2,int(math.floor(math.sqrt(x)+1))):
while x % i == 0:
x /= i
res.append(i)
if x != 1: # Unusual numbers
res.append(x)
return res
Creo que para la legibilidad y la velocidad, la solución de @ oxrock es la mejor, así que aquí está el código reescrito para Python 3+:
def num_factors(n):
results = set()
for i in range(1, int(n**0.5) + 1):
if n % i == 0: results.update([i,int(n/i)])
return results
Esta es mi solución al problema:
Tenga en cuenta que esto llevará más tiempo para números más grandes. El único problema es que solo sé cómo programar en Python 3.x. Creo que lo único que necesita cambiar es input () a raw_input ().
# Sets the number
num = int(input("Enter number to be tested."))
for i in range(1,num): # Tests all numbers from 1 to the num
while (num % i) == 0: '''''' If the remainder of the number when it is divided by i is 0''''''
print(i) # Prints i
break # Stops it from repeating one number infinitely
else: # After the previous process if done, do this
print("Finished") # Says that the process is finished
input ("Press return/enter to close the program") '''''' Closes the program after pressing enter''''''
Existe un algoritmo de fuerza industrial en SymPy llamado factorint :
>>> from sympy import factorint
>>> factorint(2**70 + 3**80)
{5: 2,
41: 1,
101: 1,
181: 1,
821: 1,
1597: 1,
5393: 1,
27188665321L: 1,
41030818561L: 1}
Esto tomó menos de un minuto. Cambia entre un cóctel de métodos. Ver la documentación vinculada arriba.
Dado todos los factores primos, todos los otros factores se pueden construir 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 la descuidado int(n**0.5)
. Por ejemplo, cuando n = 10000000000000079**2
, tenemos
>>> int(n**0.5)
10000000000000078L
Dado que 10000000000000079 es un número primo , el algoritmo de la respuesta aceptada nunca encontrará este factor. Tenga en cuenta que no se trata solo de una vez por vez; para números más grandes, estará apagado por más. Por esta razón, es mejor evitar los números de coma flotante en algoritmos de este tipo.
He intentado la mayoría de estas maravillosas respuestas con tiempo para comparar su eficacia con mi simple función y, sin embargo, constantemente veo que las mías superan a las que se enumeran aquí. Pensé que lo compartiría y vería lo que todos piensan.
def factors(n):
results = set()
for i in xrange(1, int(math.sqrt(n)) + 1):
if n % i == 0:
results.add(i)
results.add(int(n/i))
return results
Como está escrito, tendrá que importar matemática 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 de todos modos.
La respuesta de agf es realmente genial. Quería ver si podía reescribirlo para evitar el uso de reduce()
. Esto es lo que se me ocurrió:
import itertools
flatten_iter = itertools.chain.from_iterable
def factors(n):
return set(flatten_iter((i, n//i)
for i in range(1, int(n**0.5)+1) if n % i == 0))
También probé una versión que usa funciones de generador difíciles:
def factors(n):
return set(x for tup in ([i, n//i]
for i in range(1, int(n**0.5)+1) if n % i == 0) for x in tup)
Lo cronometré computando:
start = 10000000
end = start + 40000
for n in range(start, end):
factors(n)
Lo ejecuté una vez para permitir que Python lo compilara, luego lo ejecuté bajo el comando de tiempo (1) tres veces y mantuve el mejor tiempo.
- reducir versión: 11.58 segundos
- versión de itertools: 11.49 segundos
- Versión engañosa: 11.12 segundos
Tenga en cuenta que la versión de itertools está construyendo una tupla y pasándola a flatten_iter (). Si cambio el código para crear una lista, se ralentiza levemente:
- versión de iterools (lista): 11.62 segundos
Creo que la versión de funciones del generador complicado es la más rápida posible en Python. Pero no es mucho más rápido que la versión reducida, aproximadamente un 4% más rápido según mis mediciones.
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 extraños, no es necesario verificarlos cuando se trata de números impares.
Acabo de empezar a resolver acertijos Project Euler yo mismo. En algunos problemas, se llama una comprobación de divisor dentro de dos 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:
from math import sqrt
def factors(n):
step = 2 if n%2 else 1
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(sqrt(n))+1, step) if n % i == 0)))
Sin embargo, en números pequeños (~ <100), la sobrecarga adicional de esta alteración puede hacer que la función tarde más tiempo.
Ejecuté algunas pruebas para verificar la velocidad. Debajo está el código usado. Para producir las diferentes parcelas, X = range(1,100,1)
consecuencia.
import timeit
from math import sqrt
from matplotlib.pyplot import plot, legend, show
def factors_1(n):
step = 2 if n%2 else 1
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(sqrt(n))+1, step) if n % i == 0)))
def factors_2(n):
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0)))
X = range(1,100000,1000)
Y = []
for i in X:
f_1 = timeit.timeit(''factors_1({})''.format(i), setup=''from __main__ import factors_1'', number=10000)
f_2 = timeit.timeit(''factors_2({})''.format(i), setup=''from __main__ import factors_2'', number=10000)
Y.append(f_1/f_2)
plot(X,Y, label=''Running time with/without parity check'')
legend()
show()
X = rango (1,100,1)
No hay diferencia significativa aquí, pero con números más grandes, la ventaja es obvia:
X = rango (1,100,000,1000) (solo números impares)
X = rango (2,100,000,100) (solo números pares)
X = rango (1,100,000,1001) (paridad alterna)
Mejora adicional a la solución de Afg & eryksun. El siguiente fragmento de código devuelve una lista ordenada de todos los factores sin cambiar la complejidad asintótica del tiempo de ejecución:
def factors(n):
l1, l2 = [], []
for i in range(1, int(n ** 0.5) + 1):
q,r = n//i, n%i # Alter: divmod() fn can be used.
if r == 0:
l1.append(i)
l2.append(q) # q''s obtained are decreasing.
if l1[-1] == l2[-1]: # To avoid duplication of the possible factor sqrt(n)
l1.pop()
l2.reverse()
return l1 + l2
Idea: en lugar de usar la función list.sort () para obtener una lista ordenada que le da complejidad a nlog (n); Es mucho más rápido usar list.reverse () en l2 que toma O (n) complejidad. (Así es como se hace python.) Después de l2.reverse (), l2 se puede agregar a l1 para obtener la lista ordenada de factores.
Observe, l1 contiene i-s que están aumentando. l2 contiene q -s que están disminuyendo. Esa es la razón detrás de usar la idea anterior.
Para n hasta 10 ** 16 (tal vez incluso un poco más), aquí hay una solución Python 3.6 pura y rápida,
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))
Sin usar la función reducir () que no está incorporada en Python3:
def factors(n):
return list(sorted(set([f(x) for x in range(1, int(n**0.5) + 1) if not n % x
for f in (lambda x: x, lambda x: n // x)])))
Un enfoque alternativo a la respuesta de agf:
def factors(n):
result = set()
for i in range(1, int(n ** 0.5) + 1):
div, mod = divmod(n, i)
if mod == 0:
result |= {i, div}
return result
Usar el set(...)
hace que el código sea un poco más lento, y solo es realmente necesario para cuando se consulta la raíz cuadrada. Aquí está mi versión:
def factors(num):
if (num == 1 or num == 0):
return []
f = [1]
sq = int(math.sqrt(num))
for i in range(2, sq):
if num % i == 0:
f.append(i)
f.append(num/i)
if sq > 1 and num % sq == 0:
f.append(sq)
if sq*sq != num:
f.append(num/sq)
return f
La condición if sq*sq != num:
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 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 oxrock es, con mucho, el más cercano.
Use algo tan simple como la siguiente lista de comprensión, señalando que no necesitamos probar 1 y el número que estamos tratando de encontrar:
def factors(n):
return [x for x in range(2, n//2+1) if n%x == 0]
En referencia al uso de raíz cuadrada, digamos que queremos encontrar factores de 10. La porción entera del sqrt(10) = 4
por lo tanto range(1, int(sqrt(10))) = [1, 2, 3, 4]
y probando hasta 4 claramente falla 5.
A menos que me falta algo que sugeriría, si debe hacerlo de esta manera, utilizando int(ceil(sqrt(x)))
. Por supuesto, esto produce muchas llamadas innecesarias a funciones.
un algoritmo potencialmente más eficiente que los presentados aquí ya (especialmente si hay pequeños factores primarios en n
). el truco aquí es ajustar el límite hasta donde se necesita la división de prueba cada vez que se encuentran factores primos:
def factors(n):
''''''
return prime factors and multiplicity of n
n = p0^e0 * p1^e1 * ... * pk^ek encoded as
res = [(p0, e0), (p1, e1), ..., (pk, ek)]
''''''
res = []
# get rid of all the factors of 2 using bit shifts
mult = 0
while not n & 1:
mult += 1
n >>= 1
if mult != 0:
res.append((2, mult))
limit = round(sqrt(n))
test_prime = 3
while test_prime <= limit:
mult = 0
while n % test_prime == 0:
mult += 1
n //= test_prime
if mult != 0:
res.append((test_prime, mult))
if n == 1: # only useful if ek >= 3 (ek: multiplicity
break # of the last prime)
limit = round(sqrt(n)) # adjust the limit
test_prime += 2 # will often not be prime...
if n != 1:
res.append((n, 1))
return res
esto es, por supuesto, una división de prueba y nada más elegante. y por lo tanto aún muy limitado en su eficiencia (especialmente para grandes números sin divisores pequeños).
esto es python3; la división //
debería ser lo único que necesita adaptar para python 2 (agregar from __future__ import division
).
from functools import reduce
def factors(n):
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(pow(n, 0.5) + 1)) if n % i == 0)))
Esto devolverá todos los factores, muy rápidamente, de un número n
.
¿Por qué la raíz cuadrada es el límite superior?
sqrt(x) * sqrt(x) = x
. Entonces, si los dos factores son iguales, ambos son la raíz cuadrada. Si haces un factor más grande, tienes que hacer que el otro factor sea más pequeño. Esto significa que uno de los dos siempre será menor o igual que sqrt(x)
, por lo que solo tiene que buscar hasta ese punto para encontrar uno de los dos factores coincidentes. Luego puede usar x / fac1
para obtener fac2
.
La reduce(list.__add__, ...)
está tomando las listas pequeñas de [fac1, fac2]
y uniéndolas en una larga lista.
El [i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0
devuelve un par de factores, si el resto al dividir n
entre el más pequeño es cero (No es necesario que compruebe el más grande también, simplemente lo consigue dividiendo n
por el más pequeño).
El set(...)
en el exterior es deshacerse de los duplicados, lo que solo sucede con los cuadrados perfectos. Para n = 4
, esto devolverá 2
dos veces, por lo que set
se deshace de uno de ellos.
from functools import reduce
def factors(n):
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(n**0.5) + 1) if n % i == 0)))
if __name__ == "__main__":
n = int(input("enter the number to get factors"))
fact = list(factors(n))
for x in fact:
print(x)