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.

Asked By: Varun Narayanan

||

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.

Answered By: Miriam Farber

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)}
Answered By: Scorpion_Tharaka

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)
Answered By: Super_svyat

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)
Answered By: 5P1D3R_M0NK3Y

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.

Answered By: Yannick Oswald
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.