Project Euler #23 – Python Non Abundant Sums
Question:
I have been working on Project Euler #23
.
This is the task:
Problem 23
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.
This is my code:
import math
def getDivisors(num):
n = math.ceil(math.sqrt(num))
total = 1
divisor = 2
while (divisor < n):
if (num%divisor == 0):
total += divisor
total += num//divisor
divisor+=1
return total
def isAbundant(num):
if (getDivisors(num) > num):
return True
else:
return False
abundentNums = []
for x in range (0,28124):
if (isAbundant(x)):
abundentNums.append(x)
del abundentNums[0]
sums = [0]*28124
for x in range (0, len(abundentNums)):
for y in range (x, len(abundentNums)):
sumOf2AbundantNums = abundentNums[x]+abundentNums[y]
if (sumOf2AbundantNums<= 28123):
if (sums[sumOf2AbundantNums] == 0):
sums[sumOf2AbundantNums] = sumOf2AbundantNums
total = 0
for x in range (1,len(sums)):
if (sums[x] == 0):
total +=x
print('n', total)
The total value I get is 4190404
. The correct answer is 4179871
.I have spent an hour looking at my code, but I am unable to find the error. What should I change to correct the error? My answer is close. Thanks in advance
PS. I am new to python. Run time is 25s any optimisations will be useful as well.
Answers:
Your getDivisors
function is incorrect. It doesn’t count the root divisors of square numbers (for example, if num=25
, it will return 1). Here is a corrected version:
def getDivisors(num):
if num==1:
return 1
n = math.ceil(math.sqrt(num))
total = 1
divisor = 2
while (divisor < n):
if (num%divisor == 0):
total += divisor
total += num//divisor
divisor+=1
if n**2==num:
total+=n
return total
with this function I get the required result 4179871
.
I have done a couple of changes to the code mentioned above and i had the correct answer within 13 seconds. My CPU is intel core i5. Here is my code:
from array import array
import math
def find_abundant(number):
sum_factor = 1
value = math.ceil(math.sqrt(number))
if number == 1:
return False
for i in range(2, value):
if number % i == 0:
sum_factor += (i+(number//i))
if value**2 == number:
sum_factor += value
if sum_factor > number:
return True
else:
return False
numbers = [0]*28123
abundant_numbers = array("i", [])
for abundant in range(1, 28124):
if find_abundant(abundant):
abundant_numbers.append(abundant)
for x in abundant_numbers:
for y in abundant_numbers[0:abundant_numbers.index(x)+1]:
z = x+y
if z < 28124 and numbers[z-1] == 0:
numbers[z-1] = z
_sum = 0
for vary in range(1, len(numbers)+1):
if numbers[vary-1] == 0:
_sum += vary
print(_sum)}
This code works 6 seconds on my computer:
from math import sqrt
import itertools
import functools
import operator
#simplicity test
def fuc(a):
d = 1
z = 0
while d<= sqrt(a):
d = d + 1
if a == 2:
z = a
elif (a%d) == 0:
z = False
break
else:
z = a
return z
#prime number divisors
def func(value):
v = []
d = 1
value1= value# for optimization
while d <= sqrt(value1):
d+=1
if fuc(d)!= False and value1 % d == 0:
v.append(d)
value1 = value1/d
d = 1
if value1 != value and value1 != 1:
v.append(value1)
return v
# all number divisors without 1 and source number
def calculate_devisors(n):
prime_multiples_list = func(n)
unique_combinations = set()
for i in range(1, len(prime_multiples_list)):
unique_combinations.update(set(itertools.combinations(prime_multiples_list,i)))
combinations_product = list(functools.reduce(operator.mul,i) for i in unique_combinations)
combinations_product.sort()
try:
return combinations_product
except:
return []
abundentNums = []
for n in range(1,28123):
if sum(calculate_devisors(n))+1>n:
abundentNums.append(n)
sums = [0]*28124
for x in range (0, len(abundentNums)):
for y in range (x, len(abundentNums)):
sumOf2AbundantNums = abundentNums[x]+abundentNums[y]
if (sumOf2AbundantNums<= 28123):
if (sums[sumOf2AbundantNums] == 0):
sums[sumOf2AbundantNums] = sumOf2AbundantNums
ans = 0
for i in range(1,len(sums)):
if sums[i]==0:
ans+=i
print(ans)
I found these small tweaks might help reducing the overall time taken. I got the in about 4 seconds on my computer.
from math import sqrt
from itertools import compress
start = time.process_time()
abundant_nos = []
for i in range(12,28123):
factor = 0
for j in range(2,int(sqrt(i))+1):
if i%j == 0:
factor+= j + i//j
if j == sqrt(i):
factor -= j
if factor>i:
abundant_nos.append(i)
num_list = [True]*28123
k = 0
for i in abundant_nos:
for j in abundant_nos[k:]:
if(i+j>28123): break
num_list[i+j-1] = False
k+=1
answer = sum(compress(range(1,28124),num_list))
print(answer)
A possible solution with numpy that avoids the nested loop, and generally times less than 3 seconds on my laptop, is the following.
Description
Take your list of abundant Numbers and transform to numpy array A. Make an additional array containing the numbers from 1 to the limit posed in the problem B. Use an outer sum to create matrix that contains all possible sum-combinations of abundant numbers below the limit. And delete duplicate entries. And finally the difference between the sum of all numbers to limit in B and sum of all numbers that CAN be expressed as sum of two abundant numbers in C is the quantity looked for.
Code
A = np.array(abundentNums)
B = np.linspace(1,28123,28123)
C = np.unique(np.add.outer(A, A)[(np.add.outer(A, A) < 28124)])
ans = int(np.sum(B) - np.sum(C))
So therefore the entire code including the corrected getDivisors function by Miriam Farber is
import math
import numpy as np
def getDivisors(num):
if num==1:
return 1
n = math.ceil(math.sqrt(num))
total = 1
divisor = 2
while (divisor < n):
if (num%divisor == 0):
total += divisor
total += num//divisor
divisor+=1
if n**2==num:
total+=n
return total
def isAbundant(num):
if (getDivisors(num) > num):
return True
else:
return False
abundentNums = []
for x in range (0,28124):
if (isAbundant(x)):
abundentNums.append(x)
del abundentNums[0]
A = np.array(abundentNums)
B = np.linspace(1,28123,28123)
C = np.unique(np.add.outer(A, A)[(np.add.outer(A, A) < 28124)])
ans = int(np.sum(B) - np.sum(C))
A further speed tweak would be list comprehension to make the list of abundant numbers.
I have been working on Project Euler #23
.
This is the task:
Problem 23
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.
This is my code:
import math
def getDivisors(num):
n = math.ceil(math.sqrt(num))
total = 1
divisor = 2
while (divisor < n):
if (num%divisor == 0):
total += divisor
total += num//divisor
divisor+=1
return total
def isAbundant(num):
if (getDivisors(num) > num):
return True
else:
return False
abundentNums = []
for x in range (0,28124):
if (isAbundant(x)):
abundentNums.append(x)
del abundentNums[0]
sums = [0]*28124
for x in range (0, len(abundentNums)):
for y in range (x, len(abundentNums)):
sumOf2AbundantNums = abundentNums[x]+abundentNums[y]
if (sumOf2AbundantNums<= 28123):
if (sums[sumOf2AbundantNums] == 0):
sums[sumOf2AbundantNums] = sumOf2AbundantNums
total = 0
for x in range (1,len(sums)):
if (sums[x] == 0):
total +=x
print('n', total)
The total value I get is 4190404
. The correct answer is 4179871
.I have spent an hour looking at my code, but I am unable to find the error. What should I change to correct the error? My answer is close. Thanks in advance
PS. I am new to python. Run time is 25s any optimisations will be useful as well.
Your getDivisors
function is incorrect. It doesn’t count the root divisors of square numbers (for example, if num=25
, it will return 1). Here is a corrected version:
def getDivisors(num):
if num==1:
return 1
n = math.ceil(math.sqrt(num))
total = 1
divisor = 2
while (divisor < n):
if (num%divisor == 0):
total += divisor
total += num//divisor
divisor+=1
if n**2==num:
total+=n
return total
with this function I get the required result 4179871
.
I have done a couple of changes to the code mentioned above and i had the correct answer within 13 seconds. My CPU is intel core i5. Here is my code:
from array import array
import math
def find_abundant(number):
sum_factor = 1
value = math.ceil(math.sqrt(number))
if number == 1:
return False
for i in range(2, value):
if number % i == 0:
sum_factor += (i+(number//i))
if value**2 == number:
sum_factor += value
if sum_factor > number:
return True
else:
return False
numbers = [0]*28123
abundant_numbers = array("i", [])
for abundant in range(1, 28124):
if find_abundant(abundant):
abundant_numbers.append(abundant)
for x in abundant_numbers:
for y in abundant_numbers[0:abundant_numbers.index(x)+1]:
z = x+y
if z < 28124 and numbers[z-1] == 0:
numbers[z-1] = z
_sum = 0
for vary in range(1, len(numbers)+1):
if numbers[vary-1] == 0:
_sum += vary
print(_sum)}
This code works 6 seconds on my computer:
from math import sqrt
import itertools
import functools
import operator
#simplicity test
def fuc(a):
d = 1
z = 0
while d<= sqrt(a):
d = d + 1
if a == 2:
z = a
elif (a%d) == 0:
z = False
break
else:
z = a
return z
#prime number divisors
def func(value):
v = []
d = 1
value1= value# for optimization
while d <= sqrt(value1):
d+=1
if fuc(d)!= False and value1 % d == 0:
v.append(d)
value1 = value1/d
d = 1
if value1 != value and value1 != 1:
v.append(value1)
return v
# all number divisors without 1 and source number
def calculate_devisors(n):
prime_multiples_list = func(n)
unique_combinations = set()
for i in range(1, len(prime_multiples_list)):
unique_combinations.update(set(itertools.combinations(prime_multiples_list,i)))
combinations_product = list(functools.reduce(operator.mul,i) for i in unique_combinations)
combinations_product.sort()
try:
return combinations_product
except:
return []
abundentNums = []
for n in range(1,28123):
if sum(calculate_devisors(n))+1>n:
abundentNums.append(n)
sums = [0]*28124
for x in range (0, len(abundentNums)):
for y in range (x, len(abundentNums)):
sumOf2AbundantNums = abundentNums[x]+abundentNums[y]
if (sumOf2AbundantNums<= 28123):
if (sums[sumOf2AbundantNums] == 0):
sums[sumOf2AbundantNums] = sumOf2AbundantNums
ans = 0
for i in range(1,len(sums)):
if sums[i]==0:
ans+=i
print(ans)
I found these small tweaks might help reducing the overall time taken. I got the in about 4 seconds on my computer.
from math import sqrt
from itertools import compress
start = time.process_time()
abundant_nos = []
for i in range(12,28123):
factor = 0
for j in range(2,int(sqrt(i))+1):
if i%j == 0:
factor+= j + i//j
if j == sqrt(i):
factor -= j
if factor>i:
abundant_nos.append(i)
num_list = [True]*28123
k = 0
for i in abundant_nos:
for j in abundant_nos[k:]:
if(i+j>28123): break
num_list[i+j-1] = False
k+=1
answer = sum(compress(range(1,28124),num_list))
print(answer)
A possible solution with numpy that avoids the nested loop, and generally times less than 3 seconds on my laptop, is the following.
Description
Take your list of abundant Numbers and transform to numpy array A. Make an additional array containing the numbers from 1 to the limit posed in the problem B. Use an outer sum to create matrix that contains all possible sum-combinations of abundant numbers below the limit. And delete duplicate entries. And finally the difference between the sum of all numbers to limit in B and sum of all numbers that CAN be expressed as sum of two abundant numbers in C is the quantity looked for.
Code
A = np.array(abundentNums)
B = np.linspace(1,28123,28123)
C = np.unique(np.add.outer(A, A)[(np.add.outer(A, A) < 28124)])
ans = int(np.sum(B) - np.sum(C))
So therefore the entire code including the corrected getDivisors function by Miriam Farber is
import math
import numpy as np
def getDivisors(num):
if num==1:
return 1
n = math.ceil(math.sqrt(num))
total = 1
divisor = 2
while (divisor < n):
if (num%divisor == 0):
total += divisor
total += num//divisor
divisor+=1
if n**2==num:
total+=n
return total
def isAbundant(num):
if (getDivisors(num) > num):
return True
else:
return False
abundentNums = []
for x in range (0,28124):
if (isAbundant(x)):
abundentNums.append(x)
del abundentNums[0]
A = np.array(abundentNums)
B = np.linspace(1,28123,28123)
C = np.unique(np.add.outer(A, A)[(np.add.outer(A, A) < 28124)])
ans = int(np.sum(B) - np.sum(C))
A further speed tweak would be list comprehension to make the list of abundant numbers.