Rough factor calculation
You start by calculating:
m, n = 40, 42 r = (m..n).to_a.map { |z| (1..z).select { |x| z % x == 0} }
This is normal, but you do not need .to_a :
r = (m..n).map { |z| (1..z).select { |x| z % x == 0} }
This avoids the extra step of creating a temporary array of 1.2 :
(m..n).to_a
Solution structure
Let's work backwards to come up with our code. First, concentrate on determining for any given number q whether the sum of the squared factors of q itself an ideal square. Suppose we are magic_number? magic_number? method magic_number? which takes q as its only argument and returns true if q satisfies the required property, and false otherwise. Then we will calculate:
(m..n).select { |q| magic_number?(q) }
return an array of all numbers between m and n that satisfy the property. magic_number? you can write like this:
def magic_number?(q) return true if q == 1 s = sum_of_squared_factors(q) s == Math.sqrt(s).round**2 end
Calculation of the sum of squared factors
So, now we just have to write the sum_of_squared_factors method. We can use your code to obtain the following factors:
def factors(q) (1..q).select { |x| q % x == 0 } end factors(40)
and then write:
def sum_of_squared_factors(q) factors(q).reduce(0) { |t,i| t + i*i } end sum_of_squared_factors(40)
Speed ββup factor calculation
There is something else we can do to expedite the calculation of factors. If f is a factor of n , f and n/f , both are coefficients of n . (For example, since 3 is a coefficient of 42 , then 42/3 #=> 14 ). Therefore, we only need to get the smallest of each pair.
There is one exception to this rule. If n is an ideal square and f == n**0.5 , then f = n/f , so we include f only in the number of factors n (not n/f as well).
If it turns out that if f less than a pair, then f <=(n**0.5).round 3 . Therefore, we only need to check which of the numbers (1..(n**0.5).round) are factors and include their complements (if n is an ideal square, in this case we do not double (n**0.5).round ):
q = 42 arr = (1..Math.sqrt(q).round).select { |x| q % x == 0 }
so that we can write:
def factors(q) arr = (1..Math.sqrt(q)).select { |x| q % x == 0 } arr = arr.flat_map { |n| [n, q/n] } arr.pop if arr[-2] == arr[-1] arr end
Substituting arr (chaining expressions), we get a typical Ruby expression:
def factors(q) (1..Math.sqrt(q)).select { |x| q % x == 0 }. flat_map { |n| [n, q/n] }. tap { |a| a.pop if a[-2] == a[-1] } end factors(42)
See Enumerable # flat_map and Object # tap . (There is no need to sort this array. In applications where it should be sorted, simply .sort to the end of the flat_map block.)
Completion
As a result, we have the following:
def magic_number?(q) return true if q == 1 s = sum_of_squared_factors(q) s == Math.sqrt(s).round**2 end def sum_of_squared_factors(q) factors(q).reduce(0) { |t,i| t + i*i } end def factors(q) (1..Math.sqrt(q)).select { |x| q % x == 0 }. flat_map { |n| [n, q/n] }. tap { |a| a.pop if a[-2] == a[-1] } end m, n = 1, 1000 (m..n).select { |q| magic_number?(q) }
This calculation was completed in no time.
Calculate prime numbers for further calculation of factor speed
Finally, let me describe an even faster way to calculate number factors using the Prime :: prime_division method . This method splits any number into its main components. Consider, for example, n = 360 .
require 'prime' Prime.prime_division(360)
This tells us that:
360 == 2**3 * 3**2 * 5**1 #=> true
This also tells us that each factor of 360 is a product from 0 to 3 2 times 0 and 2 3 times 0 or 1 5 . Hence:
def factors(n) Prime.prime_division(n).reduce([1]) do |a,(prime,pow)| a.product((0..pow).map { |po| prime**po }).map { |x,y| x*y } end end a = factors(360).sort
We can check this:
a == (1..360).select { |n| (360 % n).zero? } #=> true
One more check:
factors(40).sort #=> [1, 2, 4, 5, 8, 10, 20, 40]
1. Instead, you can write this [*m..n] #=> [40, 41, 42] . 2. Why is there no need to convert the range to an array? Enumerable # map , which is an instance method of the Enumerable module, is available for use by each class in which Enumerable . Array is one, but (m..n).class #=> Range is another. (See second paragraph in the range ). 3. Assume that f less than n/f and f > n**0.5 , then n/f < n/(n**0.5) = n**0.5 < f , a contradiction.