How would I go about improving/making this run faster? [closed] - python

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I'm a beginner in Python trying to get better, and I stumbled across the following exercise:
Let n be an integer greater than 1 and s(n) the sum of the dividors of
n. For example,
s(12) 1 + 2 + 3 + 4 + 6 + 12 = 28
Also,
s(s(12)) = s(28) = 1 + 2 + 4 + 7 + 14 + 28 = 56
And
s(s(s(12))) = s(56) = 1 + 2 + 4 + 7 + 8 + 14 + 28 + 56 = 120
We use the notations:
s^1(n) = s(n)
s^2(n) = s(s(n))
s^3(n) = s(s(s(n)))
s^ m (n) = s(s(. . .s(n) . . .)), m times
For the integers n for which exists a positive integer k so that
s^m(n) = k * n
are called (m, k)-perfect, for instance 12 is (3, 10)-perfect since
s^3(12) = s(s(s(12))) = 120 = 10 * 12
Special categories:
For m =1 we have multiperfect numbers
A special case of the above exist for m = 1 and k = 2 which are called
perfect numbers.
For m = 2 and k = 2 we have superperfect numbers.
Write a program which finds and prints all (m, k)-perfect numbers for
m <= MAXM, which are less or equal to (<=) MAXNUM. If an integer
belongs to one of the special categories mentioned above the program
should print a relevant message. Also, the program has to print how
many different (m, k)-perfect numbers were found, what percentage of
the tested numbers they were, in how many occurrences for the
different pairs of (m, k), and how many from each special category
were found(perfect numbers are counted as multiperfect as well).
Here's my code:
import time
start_time = time.time()
def s(n):
tsum = 0
i = 1
con = n
while i < con:
if n % i == 0:
temp = n / i
tsum += i
if temp != i:
tsum += temp
con = temp
i += 1
return tsum
#MAXM
#MAXNUM
i = 2
perc = 0
perc1 = 0
perf = 0
multperf = 0
supperf = 0
while i <= MAXNUM:
pert = perc1
num = i
for m in xrange(1, MAXM + 1):
tsum = s(num)
if tsum % i == 0:
perc1 += 1
k = tsum / i
mes = "%d is a (%d-%d)-perfect number" % (i, m, k)
if m == 1:
multperf += 1
if k == 2:
perf += 1
print mes + ", that is a perfect number"
else:
print mes + ", that is a multiperfect number"
elif m == 2 and k == 2:
supperf += 1
print mes + ", that is a superperfect number"
else:
print mes
num = tsum
i += 1
if pert != perc1: perc += 1
print "Found %d distinct (m-k)-perfect numbers (%.5f per cent of %d ) in %d occurrences" % (
perc, float(perc) / MAXNUM * 100, MAXNUM, perc1)
print "Found %d perfect numbers" % perf
print "Found %d multiperfect numbers (including perfect numbers)" % multperf
print "Found %d superperfect numbers" % supperf
print time.time() - start_time, "seconds"
It works fine, but I would like suggestions on how to make it run faster.
For instance is it faster to use
I = 1
while I <= MAXM:
…..
I += 1
instead of
for I in xrange(1, MAXM + 1)
Would it be better if instead of defining s(n) as a function I put the code into the main program? etc.
If you have anything to suggest for me to read on how to make a program run faster, I'd appreciate it.
And one more thing, originally the exercise required the program to be in C (which I don't know), having written this in Python, how difficult would it be for it to be made into C?

The biggest improvements come from using a better algorithm. Things like
Would it be better if instead of defining s(n) as a function I put the code into the main program?
or whether to use a while loop instead of for i in xrange(1, MAXM + 1): don't make much difference, so should not be considered before one has reached a state where algorithmic improvements are at least very hard to come by.
So let's take a look at your algorithm and how we can drastically improve it without caring about minuscule things like whether a while loop or a for iteration are faster.
def s(n):
tsum = 0
i = 1
con = n
while i < con:
if n % i == 0:
temp = n / i
tsum += i
if temp != i:
tsum += temp
con = temp
i += 1
return tsum
That already contains a good idea, you know that the divisors of n come in pairs and add both divisors once you found the smaller of the pair. You even correctly handle squares.
It works very well for numbers like 120: when you find the divisor 2, you set the stop condition to 60, when you find 3, to 40, ..., when you find 8, you set it to 15, when you find 10, you set it to 12, and then you have only the division by 11, and stop when i is incremented to 12. Not bad.
But it doesn't work so well when n is a prime, then con will never be set to a value smaller than n, and you need to iterate all the way to n before you found the divisor sum. It's also bad for numbers of the form n = 2*p with a prime p, then you loop to n/2, or n = 3*p (n/3, unless p = 2) etc.
By the prime number theorem, the number of primes not exceeding x is asymptotically x/log x (where log is the natural logarithm), and you have a lower bound of
Ω(MAXNUM² / log MAXNUM)
just for computing the divisor sums of the primes. That's not good.
Since you already consider the divisors of n in pairs d and n/d, note that the smaller of the two (ignoring the case d = n/d when n is a square for the moment) is smaller than the square root of n, so once the test divisor has reached the square root, you know that you have found and added all divisors, and you're done. Any further looping is futile wasted work.
So let us consider
def s(n):
tsum = 0
root = int(n**0.5) # floor of the square root of n, at least for small enough n
i = 1
while i < root + 1:
if n % i == 0:
tsum += i + n/i
i += 1
# check whether n is a square, if it is, we have added root twice
if root*root == n:
tsum -= root
return tsum
as a first improvement. Then you always loop to the square root, and computing s(n) for 1 <= n <= MAXNUM is Θ(MAXNUM^1.5). That's already quite an improvement. (Of course, you have to compute the iterated divisor sums, and s(n) can be larger than MAXNUM for some n <= MAXNUM, so you can't infer a complexity bound of O(MAXM * MAXNUM^1.5) for the total algorithm from that. But s(n) cannot be very much larger, so the complexity can't be much worse either.)
But we can still improve on that by using what twalberg suggested, using the prime factorisation of n to compute the divisor sum.
First, if n = p^k is a prime power, the divisors of n are 1, p, p², ..., p^k, and the divisor sum is easily computed (a closed formula for the geometric sum is
(p^(k+1) - 1) / (p - 1)
but whether one uses that or adds the k+1 powers of p dividing n is not important).
Next, if n = p^k * m with a prime p and an m such that p does not divide m, then
s(n) = s(p^k) * s(m)
An easy way to see that decomposition is to write each divisor d of n in the form d = p^a * g where p does not divide g. Then p^a must divide p^k, i.e. a <= k, and g must divide m. Conversely, for every 0 <= a <= k and every g dividing m, p^a * g is a divisor of n. So we can lay out the divisors of n (where 1 = g_1 < g_2 < ... < g_r = m are the divisors of m)
1*g_1 1*g_2 ... 1*g_r
p*g_1 p*g_2 ... p*g_r
: : :
p^k*g_1 p^k*g_2 ... p^k*g_r
and the sum of each row is p^a * s(m).
If we have a list of primes handy, we can then write
def s(n):
tsum = 1
for p in primes:
d = 1
# divide out all factors p of n
while n % p == 0:
n = n//p
d = p*d + 1
tsum *= d
if p*p > n: # n = 1, or n is prime
break
if n > 1: # one last prime factor to account for
tsum *= 1 + n
return tsum
The trial division goes to the second largest prime factor of n [if n is composite] or the square root of the largest prime factor of n, whichever is larger. It has a worst-case bound for the largest divisor tried of n**0.5, which is reached for primes, but for most composites, the division stops much earlier.
If we don't have a list of primes handy, we can replace the line for p in primes: with for p in xrange(2, n): [the upper limit is not important, since it is never reached if it is larger than n**0.5] and get a not too much slower factorisation. (But it can easily be sped up a lot by avoiding even trial divisors larger than 2, that is using a list [2] + [3,5,7...] - best as a generator - for the divisors, even more by also skipping multiples of 3 (except 3), [2,3] + [5,7, 11,13, 17,19, ...] and if you want of a few further small primes.)
Now, that helped, but computing the divisor sums for all n <= MAXNUM still takes Ω(MAXNUM^1.5 / log MAXNUM) time (I haven't analysed, that could be also an upper bound, or the MAXNUM^1.5 could still be a lower bound, anyway, a logarithmic factor rarely makes much of a difference [beyond a constant factor]).
And you compute a lot of divisor sums more than once (in your example, you compute s(56) when investigating 12, again when investigating 28, again when investigating 56). To alleviate the impact of that, memoizing s(n) would be a good idea. Then you need to compute each s(n) only once.
And now we have already traded space for time, so we can use a better algorithm to compute the divisor sums for all 1 <= n <= MAXNUM in one go, with a better time complexity (and also smaller constant factors). Instead of trying out each small enough (prime) number whether it divides n, we can directly mark only multiples, thus avoiding all divisions that leave a remainder - which is the vast majority.
The easy method to do that is
def divisorSums(n):
dsums = [0] + [1]*n
for k in xrange(2, n+1):
for m in xrange(k, n+1, k):
dsums[m] += k
return dsums
with an O(n * log n) time complexity. You can do it a bit better (O(n * log log n) complexity) by using the prime factorisation, but that method is somewhat more complicated, I'm not adding it now, maybe later.
Then you can use the list of all divisor sums to look up s(n) if n <= MAXNUM, and the above implementation of s(n) to compute the divisor sum for values larger than MAXNUM [or you may want to memoize the values up to a larger limit].
dsums = divisorSums(MAXNUM)
def memo_s(n):
if n <= MAXNUM:
return dsums[n]
return s(n)
That's not too shabby,
Found 414 distinct (m-k)-perfect numbers (0.10350 per cent of 400000 ) in 496 occurrences
Found 4 perfect numbers
Found 8 multiperfect numbers (including perfect numbers)
Found 7 superperfect numbers
12.709428072 seconds
for
import time
start_time = time.time()
def s(n):
tsum = 1
for p in xrange(2,n):
d = 1
# divide out all factors p of n
while n % p == 0:
n = n//p
d = p*d + 1
tsum *= d
if p*p > n: # n = 1, or n is prime
break
if n > 1: # one last prime factor to account for
tsum *= 1 + n
return tsum
def divisorSums(n):
dsums = [0] + [1]*n
for k in xrange(2, n+1):
for m in xrange(k, n+1, k):
dsums[m] += k
return dsums
MAXM = 6
MAXNUM = 400000
dsums = divisorSums(MAXNUM)
def memo_s(n):
if n <= MAXNUM:
return dsums[n]
return s(n)
i = 2
perc = 0
perc1 = 0
perf = 0
multperf = 0
supperf = 0
while i <= MAXNUM:
pert = perc1
num = i
for m in xrange(1, MAXM + 1):
tsum = memo_s(num)
if tsum % i == 0:
perc1 += 1
k = tsum / i
mes = "%d is a (%d-%d)-perfect number" % (i, m, k)
if m == 1:
multperf += 1
if k == 2:
perf += 1
print mes + ", that is a perfect number"
else:
print mes + ", that is a multiperfect number"
elif m == 2 and k == 2:
supperf += 1
print mes + ", that is a superperfect number"
else:
print mes
num = tsum
i += 1
if pert != perc1: perc += 1
print "Found %d distinct (m-k)-perfect numbers (%.5f per cent of %d ) in %d occurrences" % (
perc, float(perc) / MAXNUM * 100, MAXNUM, perc1)
print "Found %d perfect numbers" % perf
print "Found %d multiperfect numbers (including perfect numbers)" % multperf
print "Found %d superperfect numbers" % supperf
print time.time() - start_time, "seconds"
By memoizing also the needed divisor sums for n > MAXNUM:
d = {}
for i in xrange(1, MAXNUM+1):
d[i] = dsums[i]
def memo_s(n):
if n in d:
return d[n]
else:
t = s(n)
d[n] = t
return t
the time drops to
3.33684396744 seconds

from functools import lru_cache
...
#lru_cache
def s(n):
...
should make it significantly faster.
[update] oh, sorry, that was added in 3.2 according to the docs. but any cache will do. see Is there a decorator to simply cache function return values?

Related

Project Euler #23 efficiency

I am trying to write a program in python to answer the following problem:
A perfect number is a number for which the sum of its proper divisors is exactly equal to the number. For example, the sum of the proper divisors of 28 would be 1 + 2 + 4 + 7 + 14 = 28, which means that 28 is a perfect number.
A number n is called deficient if the sum of its proper divisors is less
than n and it is called abundant if this sum exceeds n.
As 12 is the smallest abundant number, 1 + 2 + 3 + 4 + 6 = 16, the smallest number that can be written as the sum of two abundant numbers is 24.
By mathematical analysis, it can be shown that all integers greater than 28123 can be written as the sum of two abundant numbers. However, this upper limit cannot be reduced any further by analysis, even though it is known that the greatest number that cannot be expressed as the sum of two abundant numbers is less than this limit.
Find the sum of all the positive integers which cannot be written as the sum of two abundant numbers.
So here is my code which should theoretically work but which is way too slow.
import math
import time
l = 28123
abondant = []
def listNumbers():
for i in range(1, l):
s = 0
for k in range(1, int(i / 2) + 1):
if i % k == 0:
s += k
if s > i:
abondant.append(i)
def check(nb):
for a in abondant:
for b in abondant:
if a + b == nb:
return False
return True
def main():
abondant_sum = 0
for i in range(12, l):
if check(i):
abondant_sum += i
return abondant_sum
start = time.time()
listNumbers()
print(main())
end = time.time()
print("le programme a mis ", end - start, " ms")
How can I make my program more efficient?
Checking until half and summing up all passing numbers is very inefficient.
Try to change
for k in range(1, int(i / 2) + 1):
if i % k == 0:
s += k
to
for k in range(1, int(i**0.5)+1):
if i % k == 0:
s += k
if k != i//k:
s += i//k
The thing is that you make a double iteration on "abondant" in the check function that you call 28111 times.
It would be much more efficient to only compute a set of all a+b once and then check if your number is inside.
Something like:
def get_combinations():
return set(a+b for a in abondant for b in abondant)
And then maybe for the main function:
def main():
combinations = get_combinations()
non_abondant = filter(lambda nb: nb not in combinations, range(12,l))
return sum(non_abondant)
Once you have the list of abundant number you can make a list result = [False] * 28123 and then
for a in abondant:
for b in abondant:
result[min(a+b, 28123)] = True
Then
l = []
for i in range(len(result)):
if not result[i]:
l.append(i)
print(l)

Python : Primes in numbers

Given a positive number n > 1 find the prime factor decomposition of n. The result will be a string with the following form :
"(p1**n1)(p2**n2)...(pk**nk)"
with the p(i) in increasing order and n(i) empty if n(i) is 1.
Example: n = 86240 should return "(2**5)(5)(7**2)(11)"
This is my code , but i have problem with time for n>250000
def primeFactors(n):
a=""
i=2
while i<=n:
#part of identifying if number i is prime
w1=5
w2=2
gg=0
while w1*w1<=i:
if i%w1==0:
gg=1
break
w1+=w2
w2=6-w2
#checking if previous loop has been broken
if gg:
i+=1
continue
#countig how many thimes we can divide n on i
c=0
for j in range(1,n):
if n%i!=0: break
c+=1
n=n//i
#if we can divide n on i only once
if c==1:
a+="("+str(i)+")"
elif c>1:
a+="("+str(i)+"**"+str(c)+")"
i+=1
return a
is there a way to fix this problem?
The problem isn't prime factorization, which the program does, it's getting Python to do it for very large numbers in a very small amount of time. Specifically, my understanding is we need to be able to calculate:
for n in range(10 ** 9, 10 ** 9 + 10):
print(n, '=', primeFactors(n))
in <= 120ms or less, i.e. <= 12ms per number. The OP's code doesn't get past 10 ** 9 + 4 before milliseconds turn into seconds, seconds into minutes. My first impression of your code is you need to separate your prime logic from the rest of the code, to make it understandable, and then clean up and optimize both parts of the code. My rewrite of your program:
def generate_primes(n):
""" generate real primes """
yield(2)
primes = [(2, 4)]
for m in range(3, n, 2):
for prime, square in primes:
if square > m:
yield(m)
primes.append((m, m * m))
break
if m % prime == 0:
break
def primeFactors(n):
string = ""
i = 2
for i in generate_primes(int(n ** 0.5) + 1):
# counting how many times we can divide n on i
c = 0
while True:
product, remainder = divmod(n, i)
if remainder != 0:
break
n = product
c += 1
# if we can divide n on i only once
if c == 1:
string += f"({i})"
elif c > 1:
string += f"({i}**{c})"
if n > 1: # final prime factor greater than square root
string += f"({n})"
return string
I swapped in a prime generator to avoid redundant calculations. This optimized version can achieve 32ms per large number factored in the above test. Still not good enough. So let's try #JamesKPolk's suggestion and use pseudoprimes:
def generate_primes(n):
""" generate 5-rough pseudoprimes """
if n >= 2:
yield(2)
if n >= 3:
yield(3)
if n >= 5:
m = 1
x = 4
while m < n:
m += x
yield(m)
x = 6 - x
The tradeoff here is that we will test more divisors than we really need, but we can generate these divisors much faster. This change achieves our goal of <= 12ms per large number factored, on my machine anyway.
OUTPUT
> time python3 test.py
1000000000 = (2**9)(5**9)
1000000001 = (7)(11)(13)(19)(52579)
1000000002 = (2)(3)(43)(983)(3943)
1000000003 = (23)(307)(141623)
1000000004 = (2**2)(41**2)(148721)
1000000005 = (3)(5)(66666667)
1000000006 = (2)(500000003)
1000000007 = (1000000007)
1000000008 = (2**3)(3**2)(7)(109**2)(167)
1000000009 = (1000000009)
0.106u 0.010s 0:00.11 100.0% 0+0k 0+0io 0pf+0w
>

Divisors of a number: How can I improve this code to find the number of divisors of big numbers?

My code is very slow when it comes to very large numbers.
def divisors(num):
divs = 1
if num == 1:
return 1
for i in range(1, int(num/2)):
if num % i == 0:
divs += 1
elif int(num/2) == i:
break
else:
pass
return divs
For 10^9 i get a run time of 381.63s.
Here is an approach that determines the multiplicities of the various distinct prime factors of n. Each such power, k, contributes a factor of k+1 to the total number of divisors.
import math
def smallest_divisor(p,n):
#returns the smallest divisor of n which is greater than p
for d in range(p+1,1+math.ceil(math.sqrt(n))):
if n % d == 0:
return d
return n
def divisors(n):
divs = 1
p = 1
while p < n:
p = smallest_divisor(p,n)
k = 0
while n % p == 0:
k += 1
n //= p
divs *= (k+1)
return divs - 1
It returns the number of proper divisors (so not counting the number itself). If you want to count the number itself, don't subtract 1 from the result.
It works rapidly with numbers of the size 10**9, though will slow down dramatically with even larger numbers.
Division is expensive, multiplication is cheap.
Factorize the number into primes. (Download the list of primes, keep dividing from the <= sqrt(num).)
Then count all the permutations.
If your number is a power of exactly one prime, p^n, you obvioulsy have n divisors for it, excluding 1; 8 = 2^3 has 3 divisors: 8, 4, 2.
In general case, your number factors into k primes: p0^n0 * p1^n1 * ... * pk^nk. It has (n0 + 1) * (n1 + 1) * .. * (nk + 1) divisors. The "+1" term counts for the case when all other powers are 0, that is, contribute a 1 to the multiplication.
Alternatively, you could just google and RTFM.
Here is an improved version of my code in the question. The running time is better, 0.008s for 10^9 now.
def divisors(num):
ceiling = int(sqrt(num))
divs = []
if num == 1:
return [1]
for i in range(1, ceiling + 1):
if num % i == 0:
divs.append(num / i)
if i != num // i:
divs.append(i)
return divs
It is important for me to also keep the divisors, so if this can still
be improved I'd be glad.
Consider this:
import math
def num_of_divisors(n):
ct = 1
rest = n
for i in range(2, int(math.ceil(math.sqrt(n)))+1):
while rest%i==0:
ct += 1
rest /= i
print i # the factors
if rest == 1:
break
if rest != 1:
print rest # the last factor
ct += 1
return ct
def main():
number = 2**31 * 3**13
print '{} divisors in {}'.format(num_of_divisors(number), number)
if __name__ == '__main__':
main()
We can stop searching for factors at the square root of n. Multiple factors are found in the while loop. And when a factor is found we divide it out from the number.
edit:
#Mark Ransom is right, the factor count was 1 too small for numbers where one factor was greater than the square root of the number, for instance 3*47*149*6991. The last check for rest != 1 accounts for that.
The number of factors is indeed correct then - you don't have to check beyond sqrt(n) for this.
Both statements where a number is printed can be used to append this number to the number of factors, if desired.

How would you implement a divisor function?

The divisor function is the sum of divisors of a natural number.
Making a little research I found this to be a very good method if you want to find the divisor function of a given natural number N, so I tried to code it in Python:
def divisor_function(n):
"Returns the sum of divisors of n"
checked = [False]*100000
factors = prime_factors(n)
sum_of_divisors = 1 # It's = 1 because it will be the result of a product
for x in factors:
if checked[x]:
continue
else:
count = factors.count(x)
tmp = (x**(count+1)-1)//(x-1)
sum_of_divisors*=tmp
checked[x]=True
return sum_of_divisors
It works pretty well,but I am sure that it can be improved(e.g. : I create a list with 100000 elements,but I am not using most of them).
How would you improve/implement it?
P.S. This is prime_factors:
def prime_factors(n):
"Returns all the prime factors of a positive integer"
factors = []
d = 2
while (n > 1):
while (n%d==0):
factors.append(d)
n /= d
d = d + 1
if (d*d>n):
if (n>1): factors.append(int(n));
break;
return factors
When computing the sum of divisors, you need the factorization of n in the form p1k1 p2k2 ... — that is, you need the exponent of each prime in the factorization. At the moment you are doing this by computing a flat list of prime factors, and then calling count to work out the exponent. This is a waste of time because you can easily generate the prime factorization in the format you need in the first place, like this:
def factorization(n):
"""
Generate the prime factorization of n in the form of pairs (p, k)
where the prime p appears k times in the factorization.
>>> list(factorization(1))
[]
>>> list(factorization(24))
[(2, 3), (3, 1)]
>>> list(factorization(1001))
[(7, 1), (11, 1), (13, 1)]
"""
p = 1
while p * p < n:
p += 1
k = 0
while n % p == 0:
k += 1
n //= p
if k:
yield p, k
if n != 1:
yield n, 1
Notes on the code above:
I've transformed this code so that it generates the factorization, instead of constructing a list (by repeated calls to append) and returning it. In Python, this transformation is nearly always an improvement, because it allows you to consume elements one by one as they are generated, without having to store the whole sequence in memory.
This is the kind of function for which doctests work well.
Now computing the sum of divisors is really simple: there's no need to store the set of checked factors or to count the number of times each factor occurs. In fact you can do it in just one line:
from operator import mul
def sum_of_divisors(n):
"""
Return the sum of divisors of n.
>>> sum_of_divisors(1)
1
>>> sum_of_divisors(33550336) // 2
33550336
"""
return reduce(mul, ((p**(k+1)-1) // (p-1) for p, k in factorization(n)), 1)
You need to change two lines only:
def divisor_function(n):
"Returns the sum of divisors of n"
checked = {}
factors = prime_factors(n)
sum_of_divisors = 1 # It's = 1 because it will be the result of a product
for x in factors:
if checked.get(x,False):
continue
else:
count = factors.count(x)
tmp = (x**(count+1)-1)//(x-1)
sum_of_divisors*=tmp
checked[x]=True
return sum_of_divisors
why use dict or set - or count() - at all, when prime_factors() is guaranteed to return the factors in ascending order? You only ever deal with a previous factor. Counting can just be a part of iteration:
def divisor_function(n):
"Returns the sum of divisors of n"
factors = prime_factors(n)
sum_of_divisors = 1
count = 0; prev = 0;
for x in factors:
if x==prev:
count += 1
else:
if prev: sum_of_divisors *= (prev**(count+1)-1)//(prev-1)
count = 1; prev = x;
if prev: sum_of_divisors *= (prev**(count+1)-1)//(prev-1)
return sum_of_divisors
def sum_divisors(n):
assert n > 0
if n == 1:
return 0
sum = 1
if n % 2 == 0: # if n is even there is a need to go over even numbers
i = 2
while i < sqrt (n):
if n % i == 0:
sum = sum + i + (n//i) # if i|n then n/i is an integer so we want to add it as well
i = i + 1
if type (sqrt (n)) == int: # if sqrt(n)|n we would like to avoid adding it twice
sum = sum + sqrt (n)
else:
i = 3
while i < sqrt (n): # this loop will only go over odd numbers since 2 is not a factor
if n % i == 0:
sum = sum + i + (n//i) # if i|n then n/i is an integer so we want to add it as well
i = i + 2
if type (sqrt (n)) == int: # if sqrt(n)|n we would like to avoid adding it twice
sum = sum + sqrt (n)
return sum
Here is what I do in my Java number utilities (extensively used for Project Euler):
Generate the prime factorization with explicit exponents (see the answer by Gareth Rees).
Unfold the prime factorization in the various functions based on it. I.e., use the same algorithm as for prime factorization but directly compute the desire value instead of storing the factors and exponents.
By default test only divisors two and odd numbers. I have methods firstDivisor(n) and nextDivisor(n,d) for that.
Optionally precompute a table of least divisors for all numbers below a bound. This is very useful if you need to factorize all or most numbers below the bound (improves speed by about sqrt(limit)). I hook the table into the firstDivisor(n) and nextDivisor(n,d) methods, so this doesn't change the factorization algorithms.

How do you implement the divisor function in code?

Overall Problem: Project Euler 12 - What is the value of the first triangle number to have over five hundred divisors?
Focus of problem: The divisor function
Language: Python
Description: The function I used is brute and the time it take for the program to find a number with more divisors than x increases almost exponentially with each 10 or 20 numbers highers. I need to get to 500 or more divisors. I've identified that the divisor function is what is hogging down the program. The research I did lead me to divisor functions and specifically the divisor function which is supposed to be a function that will count all the divisors of any integer. Every page I've looked at seems to be directed toward mathematics majors and I only have high-school maths. Although I did come across some page that mentioned allot about primes and the Sieve of Atkins but I could not make the connection between primes and finding all the divisors of any integer nor find anything on the net about it.
Main Question: Could someone explain how to code the divisor function or even provide a sample? Maths concepts make more sense to me when I look at them with code. So much appreciated.
brute force divisor function:
def countdiv(a):
count = 0
for i in range(1,(a/2)+1):
if a % i == 0:
count += 1
return count + 1 # +1 to account for number itself as a divisor
If you need a bruteforce function to calculate Number of Divisors (also known as tau(n))
Here's what it looks like
def tau(n):
sqroot,t = int(n**0.5),0
for factor in range(1,sqroot+1):
if n % factor == 0:
t += 2 # both factor and N/factor
if sqroot*sqroot == n: t = t - 1 # if sqroot is a factor then we counted it twice, so subtract 1
return t
The second method involves a decomposing n into its prime factors (and its exponents).
tau(n) = (e1+1)(e2+1)....(em+1) where n = p1^e1 * p2^e2 .... pm^em and p1,p2..pm are primes
More info here
The third method and much more simpler to understand is simply using a Sieve to calculate tau.
def sieve(N):
t = [0]*(N+1)
for factor in range(1,N+1):
for multiple in range(factor,N+1,factor):
t[multiple]+=1
return t[1:]
Here's it in action at ideone
I agree with the two other answers submitted here in that you will only need to search up to the square root of the number. I have one thing to add to this however. The solutions offered will get you the correct answer in a reasonable amount of time. But when the problems start getting tougher, you will need an even more powerful function.
Take a look at Euler's Totient function. Though it only indirectly applies here, it is incredibly useful in later problems. Another related concept is that of Prime Factorization.
A quick way to improve your algorithm is to find the prime factorization of the number. In the Wikipedia article, they use 36 as an example, whose prime factorization is 2^2 * 3^2. Therefore, knowing this, you can use combinatorics to find the number of factors of 36. With this, you will not actually be computing each factor, plus you'd only have to check divisors 2 and 3 before you're complete.
When searching for divisors of n you never have to search beyond the square root of the number n. Whenever you find a divisor that's less than sqrt(n) there is exactly one matching divisor which is greater than the root, so you can increment your count by 2 (if you find divisor d of n then n/d will be the counterpart).
Watch out for square numbers, though. :) The root will be a divisor that doesn't count twice, of course.
If you're going to solve the Project Euler problems you need some functions that deal with prime numbers and integer factorization. Here is my modest library, which provides primes(n), is_prime(n) and factors(n); the focus is on simplicity, clarity and brevity at the expense of speed, though these functions should be sufficient for Project Euler:
def primes(n):
"""
list of primes not exceeding n in ascending
order; assumes n is an integer greater than
1; uses Sieve of Eratosthenes
"""
m = (n-1) // 2
b = [True] * m
i, p, ps = 0, 3, [2]
while p*p < n:
if b[i]:
ps.append(p)
j = 2*i*i + 6*i + 3
while j < m:
b[j] = False
j = j + 2*i + 3
i += 1; p += 2
while i < m:
if b[i]:
ps.append(p)
i += 1; p += 2
return ps
def is_prime(n):
"""
False if n is provably composite, else
True if n is probably prime; assumes n
is an integer greater than 1; uses
Miller-Rabin test on prime bases < 100
"""
ps = [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]
def is_spsp(n, a):
d, s = n-1, 0
while d%2 == 0:
d /= 2; s += 1
if pow(a,d,n) == 1:
return True
for r in xrange(s):
if pow(a, d*pow(2,r), n) == n-1:
return True
return False
if n in ps: return True
for p in ps:
if not is_spsp(n,p):
return False
return True
def factors(n):
"""
list of prime factors of n in ascending
order; assumes n is an integer, may be
positive, zero or negative; uses Pollard's
rho algorithm with Floyd's cycle finder
"""
def gcd(a,b):
while b: a, b = b, a%b
return abs(a)
def facts(n,c,fs):
f = lambda(x): (x*x+c) % n
if is_prime(n): return fs+[n]
t, h, d = 2, 2, 1
while d == 1:
t = f(t); h = f(f(h))
d = gcd(t-h, n)
if d == n:
return facts(n, c+1, fs)
if is_prime(d):
return facts(n//d, c+1, fs+[d])
return facts(n, c+1, fs)
if -1 <= n <= 1: return [n]
if n < -1: return [-1] + factors(-n)
fs = []
while n%2 == 0:
n = n//2; fs = fs+[2]
if n == 1: return fs
return sorted(facts(n,1,fs))
Once you know how to factor a number, it is easy to count the number of divisors. Consider 76576500 = 2^2 * 3^2 * 5^3 * 7^1 * 11^1 * 13^1 * 17^1. Ignore the bases and look at the exponents, which are 2, 2, 3, 1, 1, 1, and 1. Add 1 to each exponent, giving 3, 3, 4, 2, 2, 2, and 2. Now multiply that list to get the number of divisors of the original number 76576500: 3 * 3 * 4 * 2 * 2 * 2 * 2 = 576. Here's the function:
def numdiv(n):
fs = factors(n)
f = fs.pop(0); d = 1; x = 2
while fs:
if f == fs[0]:
x += 1
else:
d *= x; x = 2
f = fs.pop(0)
return d * x
You can see these functions at work at http://codepad.org/4j8qp60u, and learn more about how they work at my blog. I'll leave it to you to work out the solution to Problem 12.

Categories

Resources