Reputation:
I am trying to solve Problem 21 from Project Euler. I think that I've written everything correctly. However, I cannot get the final answer because the program does not get fully executed.
def d(num):
SUM = 0
for i in range(1,num):
if num % i == 0:
SUM += i
return SUM
SUM = 0
for a in range(1,10000):
for b in range(1,10000):
if a != b:
if d(a) == b and d(b) == a:
SUM += a+b
print(SUM)
Upvotes: 2
Views: 385
Reputation: 42143
You can use the same technique as the sieve of Eratosthenes to build a list of factor sums. Then match values at positions to find the amicable numbers.
# find amicable pairs (a,b) where a < b <= maxN
def findami(maxN):
sumFact = [0]*(maxN+1) # prepare factor sums list
for n in range(1,maxN//2+1): # propagate factors (up to half N)
for f in range(2*n,maxN+1,n): # add to multiples of n > 2n
sumFact[f] += n
return [(s,n) for n,s in enumerate(sumFact) if s<n and sumFact[s] == n]
Given that there is no amicable pair that crosses the 10,000 mark, you can use findami(10_000) directly to sum up the result.
print(findami(11_000),sep="\n")
(220, 284)
(1184, 1210)
(2620, 2924)
(5020, 5564)
(6232, 6368)
(10744, 10856)
print(sum(map(sum,findami(10_000)))) # takes 18 ms on my laptop
31626
Upvotes: 0
Reputation: 51998
Think of the problem from the perspective of the divisors rather than the perspective of the number being divided. A key insight is that if n < 10000
and d
is a proper divisor of n
then n
must look like n = i*j
where i < 100
and d
is one of i
or j
. Thus -- you should be able to determine all of the values of d[n]
in the range by a nested loop, where the outer loop executes just 100 times. The idea is to create a dictionary of all candidate n
(with values initialized to 0), loop over all candidate i
, and then add i
(and j
if appropriate) to the value d[n]
. The following works (and solves the problem for numbers other than 100000):
import math
def divisor_sums(k):
s = math.ceil(math.sqrt(k))
d = {n:1 for n in range(2,k)}
d[1] = 0 #special case
for i in range(2,s):
for j in range(2,k//i):
n = i*j
d[n] += i
if s <= j: d[n] += j
return d
def amicables(k):
d = divisor_sums(k)
pairs = []
for a in range(2,k):
b = d[a]
if a != b and b < k and a == d[b] : pairs.append((a,b))
return pairs
def amicable_sum(k):
pairs = amicables(k)
return sum(a+b for a,b in pairs if a < b)
For example:
>>> amicable_sum(10000)
31626
>>> amicable_sum(100000)
852810
>>> amicable_sum(1000000)
25275024
The first one was instantaneous, the second one took less than a second, and the third one about 5 seconds (on my machine).
Upvotes: 1
Reputation: 1092
There seems to be too much computation going on here. You have the second nested loop, with 10000*10000 execution, and each is calculating d twice, resulting in 10000 steps on average in the inner loop. This is 10^12 steps. I believe this will be slow even on quite potent machines. Can you think of a way to reduce the time complexity of your solution?
Upvotes: 0
Reputation: 83527
This isn't a python problem. Your algorithm will run slow no matter what language it is written in because you have three nested for loops when you can reduce it to only two. The problem is that you are calculating d(x)
100000000 times for each value of x
in the range from 1 to 10000. You should only calculate it once for each x
.
Upvotes: 0
Reputation: 641
The for b in range(1, 10000)
loop is unnecessary, because you know that there is at most one appropriate b
, and if it exists, it equals d(a)
.
Also, beware of counting each a
and b
twice in your final SUM
.
SUM = 0
for a in range(1,10000):
b = d(a)
if a != b and d(b) == a:
SUM += a+b
print(SUM//2)
Upvotes: 3