Sometimes it is easier to solve to more general problem than to solve the problem at hand. So I look for prime factors and the calculate all possible products between them. In this case, it's also x40 faster. I also took note from @tstanisl to allow you to limit the amount of work done.
You can use divisors()
for a sorted list of divisors, then look for the nearest one.
from itertools import chain, combinations
from functools import reduce # Valid in Python 2.6+, required in Python 3
import operator
def prime_factors(n, up_to=None):
"""
Returns prime factors for 'n' up to 'up_to', excluding 1 (unless n == 1)
as a sequence of tuples '(b, e)', 'b' being the factor and 'e' being
the exponent of that factor.
"""
if up_to is None:
up_to = n
for i in range(2, min(n, up_to)):
if n % i == 0:
factors = prime_factors(n//i, up_to=up_to)
if factors:
# we always get the smallest factor last, so if it is
# the same as the current number we're looking at,
# add up the exponent
last_factor, last_exp = factors[-1]
if last_factor == i:
return factors[:-1] + ((i, last_exp+1),)
return factors + ((i,1),)
if up_to is not None and up_to < n:
return tuple()
return ((n,1),)
# thanks to https://docs.python.org/dev/library/itertools.html#itertools-recipes
def powerset(iterable):
"""
Generates the powerset of a given iterable.
>>> list(powerset([1,2,3]))
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
"""
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
# thanks to https://stackoverflow.com/questions/595374/whats-the-function-like-sum-but-for-multiplication-product
def prod(t):
return reduce(operator.mul, t, 1)
def divisors(n, up_to=None):
"""
Returns a sorted list of divisors of 'n'. If 'up_to' is specified,
only prime factors up to 'up_to' will be considered when calculating
the list of divisors.
"""
return [1] + sorted([
prod(fs)
for comb in powerset(prime_factors(n, up_to))
if comb
for fs in itertools.product(*(
tuple(b**ei for ei in range(1,e+1))
for b,e in comb))
])
# >>> divisors(2040906)
# [1, 2, 3, 6, 7, 14, 21, 42, 48593, 97186,
# 145779, 291558, 340151, 680302, 1020453, 2040906]
# >>> divisors(2040906, 48592)
# [1, 2, 3, 6, 7, 14, 21, 42]
# >>> %timeit divisors(2040906)
# 100 loops, best of 5: 3.93 ms per loop
# >>> %timeit getDivisors(2040906) # from answer by @calestini
# 10 loops, best of 5: 170 ms per loop