Yes, you can optimize quite a lot.
If you found a divider, then you actually found - in most cases - two. For example, if the number is 100 and I found the divisor 2, then I also found the divisor 50 (result of 100 / 2). With this I save an iteration of for
for each divisor found.
The only exception is when the number is a perfect square: if the number is 100 and I found the divisor 10, I cannot consider that 100 / 10 is another divisor, otherwise I will be counting the 10 twice.
Another detail is that, in doing so, the iteration can go to the square root of the number, instead of going halfway. For if a certain number n
has splitters, so it can be written as the product a * b
(whereas a
and b
are dividers of n
). If both (both a
how much b
) are larger than the square root of n
, then a * b
would be greater than n
, so one of them has to be smaller than the square root. So if I found one of them (the smaller than the square root), I have also found the larger, as already described above. Therefore the loop can go to the square root.
Would look like this:
from math import sqrt
def divisores(num):
yield 1 # 1 é divisor de qualquer número
for i in range(2, int(sqrt(num)) + 1):
if num % i == 0:
yield i # encontrei um divisor
outro_divisor = num // i
if outro_divisor != i: # não é quadrado perfeito
yield outro_divisor # retorna o outro divisor
yield num # o próprio número é divisor dele mesmo
One detail is that the dividers will not be in ascending order, but then it would be enough to order them (sorted(divisores(num))
).
Doing a test with the module timeit
and comparing with the solutions of the other answers:
# loop simples (testa um a um)
def divisores(num):
for i in range(1, num // 2 + 1):
if num % i == 0:
yield i
yield num
# otimizado (conforme descrição acima)
from math import sqrt
def divisores_otimizado(num):
yield 1 # 1 é divisor de qualquer número
for i in range(2, int(sqrt(num)) + 1):
if num % i == 0:
yield i
outro_divisor = num // i
if outro_divisor != i:
yield outro_divisor
yield num
# numpy, sugestão de uma das respostas
import numpy as np
def divisores_numpy(num):
n = np.arange(1,num)
d = num % n
zeros = d == 0
return n[zeros]
from timeit import timeit
# executa 1 vez cada teste
params = { 'number' : 1, 'globals': globals() }
num = 47587950
# loop simples
print(timeit('for _ in divisores(num): pass', **params))
# numpy
print(timeit('for _ in divisores_numpy(num): pass', **params))
# meu código
print(timeit('for _ in divisores_otimizado(num): pass', **params))
# meu código, porém ordenando o resultado
print(timeit('for _ in sorted(divisores_otimizado(num)): pass', **params))
The timeit
returns time in seconds, and of course it varies from one machine to another, as it depends on several factors (hardware, if there were other things running on the machine, etc). But anyway, in mine I got close results of this:
3.7604584
0.2803169999999997
0.0007887000000001976
0.0007956000000000074
That is, the loop simple testing one by one took more than 3 seconds, the numpy
It took less than 3 tenths of a second, and my code took less than 800 microseconds (that is, less than 0.8 milliseconds). The version that sorts the divisors was a little slower, but since the divisor list is small (in this case, it has only 36 numbers), the sort does not interfere so much with the final result (there was an increase of a few microseconds).
That is, this solution (whether ordering or not) was around 4700 times faster than the loop simple and about 350 times faster than the numpy
. Testing on Ideone.com and in the Repl.it, times change, but the results are similar: numpy
slower, and the loop simple much slower still (Obs: no Repl.it had to test with a smaller number because the numpy
was apparently bursting the memory).
The difference is great because the larger the number in question, the greater the difference between the number of iterations made. In this case, the number is 47587950 (more than 47 million). The loop simple goes up to half the number, so more than 23 million iterations are made. The optimized algorithm goes to the square root of the number, only making 6898 iterations - that is, about 3400 times less iterations.
And the higher the number, the more this difference increases (for example, if the number is 100 million, it will be 50 million iterations in the loop simple versus 10,000 iterations in optimized - about 7200 times less iterations). And in the case of numpy
, no matter how fast he is, is still being created an array with millions of elements and iterating for all, so it is also slower.
Why delete the line
yield num
and the number itself is divisor of itself? The same should be in the return of the function also.– Woss
Ha that silly, it is true, if it is to include himself of course yes. Thank you for the repair @Andersoncarloswoss
– Miguel