Original Problem
Let d(n) be defined as the sum of proper divisors of n (numbers less than n which divide evenly into n). If d(a) = b and d(b) = a, where a b, then a and b are an amicable pair and each of a and b are called amicable numbers.
For example, the proper divisors of 220 are 1, 2, 4, 5, 10, 11, 20, 22, 44, 55 and 110; therefore d(220) = 284. The proper divisors of 284 are 1, 2, 4, 71 and 142; so d(284) = 220.
Evaluate the sum of all the amicable numbers under 10000.
I solved the problem by generating a hash of all the numbers between 1 - 10000 and their corresponding divisors sum (ie hash[220] = 284). I then compared the items in the hash with a copy of the hash... anyways, it works, but it takes a long time. How can I make this faster?
def proper_divs_sum num
divs = [1]
for i in 2..((num/2) + 1)
if num % i == 0
divs.push i
end
end
divs_sum = 0
divs.each do |div|
divs_sum += div
end
return divs_sum
end
def n_d_hash_gen num
nd_hash = {}
for i in 1..num
nd_hash[i] = proper_divs_sum(i)
end
return nd_hash
end
def amicables num
amicable_list = []
hash1 = n_d_hash_gen(num)
hash2 = n_d_hash_gen(num)
hash1.each do |item1|
hash2.each do |item2|
if item1 != item2 && (item1[0] == item2[1] && item2[0] == item1[1])
amicable_list.push item1
end
end
end
return amicable_list
end
Also, I am new to Ruby, so any tips on how to make this more Ruby-like would also be much appreciated.
Projecteuler-solutions provides merely a list of answers -- that is, it does not provide solutions or code for individual problems.
Participants can track their progress through achievement levels based on the number of problems solved. A new level is reached for every 25 problems solved. Special awards exist for solving special combinations of problems. For instance, there is an award for solving fifty prime numbered problems.
Project Euler is a series of challenging mathematical/computer programming problems that will require more than just mathematical insights to solve. Although mathematics will help you arrive at elegant and efficient methods, the use of a computer and programming skills will be required to solve most problems.
The function d(n) (more commonly known as σ(n)) is a variant of the divisor function, and it has an important property which lets you calculate it much more efficiently. It is a multiplicative function, which means that if n = ab, where a and b are coprime, then d(n) = d(a) d(b).
This means that if you can calculate d(pk) where p is prime, then d(n) = d(p1k1) ... d(prkr), where n = p1k1...prkr is the prime factorization of n. In fact, it turns out that d(pk) = (pk+1 - 1) / (p - 1), so d(n) = ∏i (piki+1 - 1) / (pi - 1).
So to calculate d(n) efficiently for all 1 ≤ n ≤ 10000, you can use a sieve to calculate the prime factorizations of all n, and then use the formula above to calculate d(n) using the prime factorization.
Once you've done that, all you need is a simple loop to calculate the sum of all n for which d(d(n)) = n.
This can even be optimized further, by combining the sieving step with the calculation of d(n), but I'll leave that as an exercise for the interested. It is not necessary for the size of this particular problem.
There are a couple of things you can do to improve your algorithm:
1) There is no need to loop to n/2 when you compute the divisors. Stop at sqrt(2) instead. By that point you have found half the divisors; the other half are computed as n divided by the first half.
2) When you enter a number in the hash table, you can immediately check if its amicable twin is already in the hash table. No need for two hash tables, or for two nested loops comparing them.
The approach you are taking is to start with a dividing, find its divisors, sum them up, and store them. You'll notice that the method you are using to find the divisors is a naïve one—I don't say this as an insult; it's only to say that your approach doesn't use any information it may have available, and only tries every number to see if it is a divisor. It does this by using modular division, and, in almost every case, the majority of candidates fail the test.
Consider if you never had to try numbers that could fail a test like this. In fact, starting with the divisors and building up the dividends from there would skirt the issue altogether.
You can do this by looping through every number <= 5000. These are your divisors, the multiples of which are your dividends. Then add the divisor to the sum of divisors for each multiple.
This approach works up the sums bit-by-bit; by the time you've worked through every divisor, you'll have an array mapping dividend to divisor. From there, you can use a method like you already have to search for amicable numbers in this list.
Division is a slow process. In your approach you are doing a lot of it, therefor your program is slow.
First of all in trying to find all divisors of a number you are trying all divisors not larger than half that number as potential divisors. You can improve on that by not going further than the square root of the number. If a number is divisible by a number larger than it's square root, the result of the division will be smaller than the square root. This will eliminate some unnecessary divisions.
Also if a number is not divisble by 2 it will also be not divisble by 4, 6, 8 etc. It is better to just divide by primes and build the possible divisors from those.
However, the problem can be solved by doing no divisions at all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With