Counting in binary ruby - arrays

I have two arrays
[a0 b0 c0]
[a1 b1 c1]
I want to calculate all the possible sums between the two. A possible sum consists of only 1 element for each column slot. For example a possible sum is
a0 + b1 + c1
or
a1 + b1 + c1
but not a1 + a0 + b0 + c0
In other words the sum in the example will have 3 slots, each one having only 1 element of the two arrays. From my point of view this looks like counting in binary, where each slot can take only 1 out of two numbers (0 or 1). So in this example
000 means all the elements in the sum come from the first array
sum(000) = a0 + b0 + c0.
sum(111) = a1 + b1 + c1
sum(010) = a0 + b1 + c0
you get the memo.
I would like to know how can I do this in ruby. I'm thinking of a complicated solution where I count in a binary string and for each count I "select" the correct elements from the arrays. Since I want all the possible combinations (2^n), can I code this in a single line or close to that?

▶ a1 = [11,12,13]
#⇒ [11, 12, 13]
▶ b1 = [21,22,23]
#⇒ [21, 22, 23]
▶ a1.zip(b1).reduce(&:product).map(&:flatten)
#⇒ [[11, 12, 13], [11, 12, 23], [11, 22, 13], [11, 22, 23],
#⇒ [21, 12, 13], [21, 12, 23], [21, 22, 13], [21, 22, 23]]
▶ a1.zip(b1).reduce(&:product).map(&:flatten).map { |e| e.reduce &:+ }
#⇒ [36, 46, 46, 56, 46, 56, 56, 66]
UPD Just out of curiosity, this is #pangpang’s solution written in ruby:
[0,1].repeated_permutation([a1.length, a2.length].min).map do |bits|
bits.each_with_index.reduce(0) do |memo, (e, i)|
memo + (e.zero? ? a1[i] : a2[i])
end
end

arr1 = [0,0,0]
arr2 = [1,1,1]
(0..(2**arr1.length-1)).each do |i|
sum = 0
bina = "%0#{arr1.length}b" % i # convert int to binary
bina.split("").each_with_index do |e,i|
e.to_i == 0 ? sum += arr1[i] : sum += arr2[i]
end
puts "#{bina} and #{sum}"
end
output:
000 sum 0
001 sum 1
010 sum 1
011 sum 2
100 sum 1
101 sum 2
110 sum 2
111 sum 3

Here is a brute force approach. I'm sure that there is a way more elegant way to do this with a lambda but my brain doesn't work that way at this time of day:
2.1.2 :003 > a=[1,2,3]
=> [1, 2, 3]
2.1.2 :005 > b=[4,5,6]
=> [4, 5, 6]
2.1.2 :006 > 1.downto(0) do |outer|
2.1.2 :007 > 1.downto(0) do |middle|
2.1.2 :008 > 1.downto(0) do |inner|
2.1.2 :009 > puts (outer==1 ? b[0] : a[0]) + (middle==1 ? b[1] : a[1]) + (inner==1 ? b[2] : a[2])
2.1.2 :010?> end
2.1.2 :011?> end
2.1.2 :012?> end
15
12
12
9
12
9
9
6

Here's another way to implement #pangpang's answer. I've also attempted to explain the basic idea of this approach.
Code
def perm_sums(arr0, arr1)
sz = arr0.size
at = [arr0, arr1].transpose
(0...2**sz).map { |n| sz.times.reduce(0) { |t,i| t + at[i][n[i]] } }
end
Example
arr0 = [1,2,3]
arr1 = [6,7,8]
perm_sums(arr0, arr1) #=> [6, 11, 11, 16, 11, 16, 16, 21]
Explanation
For the example above:
sz = arr0.size #=> 3
at = [arr0, arr1].transpose #=> [[1, 6], [2, 7], [3, 8]]
This is of course the same as arr0.zip(arr1).
e0 = (0...2**sz).map #=> #<Enumerator: 0...8:map>
We can view the elements of this enumerator by converting it to an array:
e0.to_a #=> [0, 1, 2, 3, 4, 5, 6, 7]
The first element of e0 is passed to the block and assigned to the block variable:
n = e0.next #=> 0
n=0 is not so interesting, as its binary representation is all zero bits. Let's instead look at n=3:
n = e0.next #=> 1
n = e0.next #=> 2
n = e0.next #=> 3
e1 = sz.times #=> #<Enumerator: 3:times>
e1.to_a #=> [0, 1, 2]
The block calculations make use of Fixnum#[]. The binary representation of n=3 is shown by the string:
3.to_s(2).rjust(sz,'0') #=> "011"
3[i] gives the ith most significant digit of the binary value:
3[0] #=> 1
3[1] #=> 1
3[2] #=> 0
Block calculations proceed as follows. reduce sets the block variable t to the initial value of 0 and then passes each of the three elements of e1 to the block:
t = 0
i = e1.next #=> 0
t + at[i][n[i]] #=> 0 + at[0][n[0]] => [1, 6][3[0]] => [1, 6][1] => 6
t = 6
i = e1.next #=> 1
t + at[i][n[i]] #=> 1 + at[1][3[1]] => 1 + [2,7][1] => 8
t = 8
i = e1.next #=> 2
t + at[i][n[i]] #=> 8 + at[2][n[2]] => 8 + [3,8][3[2]] => 8 + [3,8][0] => 11
i = e1.next
#=> StopIteration: iteration reached an end
So the number 3 is mapped to 11. Other calculations are performed similarly.
Note that we would get the same answer if we replaced at[i][n[i]] with at[i][n[sz-1-i]] (i.e., extracting bits from high to low).

Related

Numpy: How to slice or split 2D subsections of 2D array

I have a 2D array. For example:
ary = np.arange(24).reshape(6,4)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]
I want to break this into smaller 2D arrays, each 2x2, and compute the square root of the sum of each. I actually want to use arbitrary sized sub-arrays, and compute arbitrary functions of them, but I think this question is easier to ask with concrete operations and concrete array sizes, so in this example starting with a 6x4 array and computing the square root of sums of 2x2 sub-arrays, the final result would be a 3x2 array, as follows:
[[3.16, 4.24] # math.sqrt(0+1+4+5) , math.sqrt(2+3+6+7)
[6.48, 7.07] # math.sqrt(8+9+12+13) , math.sqrt(10+11+14+15)
[8.60, 9.05]] # math.sqrt(16+17+20+21), math.sqrt(18+19+22+23)
How can I slice, or split, or do some operation to perform some computation on 2D sub-arrays?
Here is a working, inefficient example of what I'm trying to do:
import numpy as np
a_height = 6
a_width = 4
a_area = a_height * a_width
a = np.arange(a_area).reshape(a_height, a_width)
window_height = 2
window_width = 2
b_height = a_height // window_height
b_width = a_width // window_width
b_area = b_height * b_width
b = np.zeros(b_area).reshape(b_height, b_width)
for i in range(b_height):
for j in range(b_width):
b[i, j] = a[i * window_height:(i + 1) * window_height, j * window_width:(j + 1) * window_width].sum()
b = np.sqrt(b)
print(b)
# [[3.16227766 4.24264069]
# [6.4807407 7.07106781]
# [8.60232527 9.05538514]]
In [2]: ary = np.arange(24).reshape(6,4)
In [3]: ary
Out[3]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]])
While I recommended moving-windows based on as_strided, we can also divide the array into 'blocks' with reshape and transpose:
In [4]: ary.reshape(3,2,2,2).transpose(0,2,1,3)
Out[4]:
array([[[[ 0, 1],
[ 4, 5]],
[[ 2, 3],
[ 6, 7]]],
[[[ 8, 9],
[12, 13]],
[[10, 11],
[14, 15]]],
[[[16, 17],
[20, 21]],
[[18, 19],
[22, 23]]]])
In [5]: np.sqrt(_.sum(axis=(2,3)))
Out[5]:
array([[3.16227766, 4.24264069],
[6.4807407 , 7.07106781],
[8.60232527, 9.05538514]])
While the transpose makes it easier to visual the blocks that need to be summed, it isn't necessary:
In [7]: np.sqrt(ary.reshape(3,2,2,2).sum(axis=(1,3)))
Out[7]:
array([[3.16227766, 4.24264069],
[6.4807407 , 7.07106781],
[8.60232527, 9.05538514]])
np.lib.stride_tricks.sliding_window doesn't give us as much direct control as I thought, but
np.lib.stride_tricks.sliding_window_view(ary,(2,2))[::2,::2]
gives the same result as Out[4].
In [13]: np.sqrt(np.lib.stride_tricks.sliding_window_view(ary,(2,2))[::2,::2].sum(axis=(2,3)))
Out[13]:
array([[3.16227766, 4.24264069],
[6.4807407 , 7.07106781],
[8.60232527, 9.05538514]])
[7] is faster.
In general, it can be done like this:
a_height = 15
a_width = 16
a_area = a_height * a_width
a = np.arange(a_are).reshape(a_height, a_width)
window_height = 3 # must evenly divide a_height
window_width = 4 # must evenly divide a_width
b_height = a_height // window_height
b_width = a_width // window_width
b = a.reshape(b_height, window_height, b_width, window_width).transpose(0,2,1,3)
# or, assuming you want sum or another function that takes `axis` argument
b = a.reshape(b_height, window_height, b_width, window_width).sum(axis=(1,3))

Merge two ordered arrays into one ordered array

I am writing a method that takes two sorted arrays and I want it to return a merged array with all the values sorted. Given the two arrays below:
array_one = [3, 4, 8]
array_two = [1, 5, 7]
I want my merge_arrays method to return:
[1, 3, 4, 5, 7, 8]
My current algorithm is below:
def merge_arrays(array_one, array_two)
merged_array_size = array_one.length + array_two.length
merged_array = []
current_index_on_one = 0
current_index_on_two = 0
current_merged_index = 0
for i in (0..merged_array_size - 1)
if array_one[current_index_on_one] < array_two[current_index_on_two]
merged_array[current_merged_index] = array_one[current_index_on_one]
current_index_on_one += 1
current_merged_index += 1
else
merged_array[current_merged_index] = array_two[current_index_on_two]
current_index_on_two += 1
current_merged_index += 1
end
end
return merged_array
end
I am getting an error 'undefined method `<' for nil:NilClass'. I don't understand how the conditional is receiving this. I debugged the variables in the conditionals and they are giving true or false values. I'm not sure what is causing this error.
Maybe I am missing the point but you can do:
(array_one + array_two).sort
=> [1, 3, 4, 5, 7, 8]
I am getting an error 'undefined method `<' for nil:NilClass'. I don't understand how the conditional is receiving this.
You start by comparing index 0 to index 0:
[3, 4, 8] [1, 5, 7]
0-----------0 #=> 3 < 1
Then you increment the lower value's index by 1:
[3, 4, 8] [1, 5, 7]
0--------------1 #=> 3 < 5
And so on:
[3, 4, 8] [1, 5, 7]
1-----------1 #=> 4 < 5
[3, 4, 8] [1, 5, 7]
2--------1 #=> 8 < 5
[3, 4, 8] [1, 5, 7]
2-----------2 #=> 8 < 7
At that point you get:
[3, 4, 8] [1, 5, 7]
2--------------3 #=> 8 < nil
Index 3 is outside the array's bounds, so array_two[current_index_on_two] returns nil and:
if array_one[current_index_on_one] < array_two[current_index_on_two]
# ...
end
becomes
if 8 < nil
# ...
end
resulting in ArgumentError(comparison of Integer with nil failed). If nil is on the left hand side, you'd get NoMethodError (undefined method `<' for nil:NilClass).
Here's one way you can write merge using recursion. Note, as you specified, both inputs must already be sorted otherwise the output will be invalid. The inputs can vary in size.
def merge (xs, ys)
if xs.empty?
ys
elsif ys.empty?
xs
else
x, *_xs = xs
y, *_ys = ys
if x < y
[x] + (merge _xs, ys)
else
[y] + (merge xs, _ys)
end
end
end
merge [ 1, 3, 4, 6, 8, 9 ], [ 0, 2, 5, 7 ]
# => [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Assuming you have two sorted arrays. You need to create pipeline using recursion going to crunch through each array. checking at each iteration to see
which value at index 0 of either array is lower, removing that from the array and appending that value to the result array.
def merge_arrays(a, b)
# build a holder array that is the size of both input arrays O(n) space
result = []
# get lower head value
if a[0] < b[0]
result << a.shift
else
result << b.shift
end
# check to see if either array is empty
if a.length == 0
return result + b
elsif b.length == 0
return result + a
else
return result + merge_arrays(a, b)
end
end
> a = [3, 4, 6, 10, 11, 15]
> b = [1, 5, 8, 12, 14, 19]
> merge_arrays(a, b)
#=> [1, 3, 4, 5, 6, 8, 10, 11, 12, 14, 15, 19]
I made slight changes to your code in order to make it work. See the comments inside.
array_one = [2, 3, 4, 8, 10, 11, 12, 13, 15]
array_two = [1, 5, 6, 7, 9, 14]
def merge_arrays(array_one, array_two)
array_one, array_two = array_two, array_one if array_one.length > array_two.length # (1) swap arrays to make satement (3) work, need array_two always be the longest
merged_array_size = array_one.length + array_two.length
merged_array = []
current_index_on_one = 0
current_index_on_two = 0
current_merged_index = 0
for i in (0...merged_array_size-1) # (2) three points to avoid the error
if (!array_one[current_index_on_one].nil? && array_one[current_index_on_one] < array_two[current_index_on_two]) # (3) check also if array_one is nil
merged_array[current_merged_index] = array_one[current_index_on_one]
current_index_on_one += 1
current_merged_index += 1
else
merged_array[current_merged_index] = array_two[current_index_on_two]
current_index_on_two += 1
current_merged_index += 1
end
end
merged_array[current_merged_index] = array_one[current_index_on_one] || array_two[current_index_on_two] # (4) add the missing element at the end of the loop, looks what happen if you comment out this line
return merged_array
end
p merge_arrays(array_one, array_two)
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
The error was coming because the loop was making one step over. The solution is to stop before and insert the missing element at the end of the loop.
It works also with:
# for i in (1...merged_array_size)
# and
# for i in (1..merged_array_size-1)
# and
# (merged_array_size-1).times do
arr1 = [3, 4, 8, 9, 12]
arr2 = [1, 5, 7, 8, 13]
arr = [arr1, arr2]
idx = [0, 0]
(arr1.size + arr2.size).times.with_object([]) do |_,a|
imin = [0, 1].min_by { |i| arr[i][idx[i]] || Float::INFINITY }
a << arr[imin][idx[imin]]
idx[imin] += 1
end
#=> [1, 3, 4, 5, 7, 8, 8, 9, 12, 13]

Removing duplicates as well as the corresponding values from array in Ruby

I'm using Ruby 1.9.3 and I want to remove values from an array that appear more than once. I have the following:
arr = [1,2,2,3,4,5,6,6,7,8,9]
and the result should be:
arr = [1,3,4,5,7,8,9].
What would be the simplest, shortest Ruby code to accomplish this?
As #Sergio Tulentsev mentioned combination of group_by and select will do the trick
Here you go
arr.group_by{|i| i}.select{|k, v| v.count.eql?(1)}.keys
We can achieve this by array select and count methods
arr.select { |x| arr.count(x) == 1 } #=> [1, 3, 4, 5, 7, 8, 9]
def find_duplicates(elements)
encountered = {}
# Examine all elements in the array.
elements.each do |e|
# If the element is in the hash, it is a duplicate.
if encountered[e]
#Remove the element
else
# Record that the element was encountered.
encountered[e] = 1
end
end
end
I want to remove values from an array that appear more than once.
to check element appear more than once use Array#count
to remove element conditionally use Array#delete_if
below is an example:
> arr.delete_if{|e| arr.count(e) > 1}
#=> [1, 3, 4, 5, 7, 8, 9]
Option2:
> arr.group_by{|e| e}.delete_if{|_,v| v.size > 1}.keys
#=> [1, 3, 4, 5, 7, 8, 9]
First of you need to group elements by itself (which will return key, value pair), then remove such elements which appear more than once(value), and use keys
I would be inclined to use a counting hash.
Code
def single_instances(arr)
arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }.
select { |_,v| v == 1 }.
keys
end
Example
single_instances [1,2,2,3,4,5,6,6,7,8,9]
#=> [1, 3, 4, 5, 7, 8, 9]
Explanation
The steps are as follows.
arr = [1,2,2,3,4,5,6,6,7,8,9]
f = Hash.new(0)
#=> {}
f is created with the method Hash::new with an argument of zero. That means that if f does not have a key k, f[k] returns zero (and does not alter f).
enum = arr.each_with_object(f)
#=> #<Enumerator: [1, 2, 2, 3, 4, 5, 6, 6, 7, 8, 9]:each_with_object({})>
h = enum.each { |e,h| h[e] += 1 }
#=> {1=>1, 2=>2, 3=>1, 4=>1, 5=>1, 6=>2, 7=>1, 8=>1, 9=>1}
g = h.select { |_,v| v == 1 }
#=> {1=>1, 3=>1, 4=>1, 5=>1, 7=>1, 8=>1, 9=>1}
g.keys
#=> [1, 3, 4, 5, 7, 8, 9]
In calculating g, Hash#select (which returns a hash), not Enumerable#select (which returns an array), is executed. I've used an underscore for the first block variable (a key in h) to signify that it is not used in the block calculation.
Let's look more carefully at the calculation of h. The first value is generated by the enumerator enum and passed to the block, and the block variables are assigned values using a process called disambiguation or decomposition.
e, h = enum.next
#=> [1, {}]
e #=> 1
h #=> {}
so the block calculation is
h[e] += 1
#=> h[e] = h[e] + 1 => 0 + 1 => 1
h[e] on the right side of the equality (using the method Hash#[], as contrasted with Hash#[]= on the left side of the equality), returns 1 because h has no key e #=> 1.
The next two elements of enum are passed to the block and the following calculations are performed.
e, h = enum.next
#=> [2, {1=>1}]
h[e] += 1
#=> h[e] = h[2] + 1 => 0 + 1 => 1
Notice that h has been updated.
e, h = enum.next
#=> [2, {1=>1, 2=>1}]
h[e] += 1
#=> h[e] = h[e] + 1 => h[2] + 1 => 1 + 1 => 2
h #=> {1=>1, 2=>2}
This time, because h already has a key e #=> 2, the hash's default value is not used.
The remaining calculations are similar.
Use [Array#difference] instead
A simpler way is to use the method Array#difference.
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
Suppose
arr = [1,2,2,3,4,2,5,6,6,7,8,9]
Note the addition of a third 2.
arr - arr.difference(arr.uniq)
# => [1, 3, 4, 5, 7, 8, 9]
The three steps are as follows.
a = arr.uniq
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
b = arr.difference(a)
#=> [2, 2, 6] (elements that appear more than once)
arr - b
# => [1, 3, 4, 5, 7, 8, 9]
I've proposed that Array#diffence be added to the Ruby core, but there seems to be little interest in doing so.

iterate over array to find int over 10 and add those two digits together

Trying to iterate over and array and for any digit 10 or higher, split those digits and add them together for instance: 10 > "1" "0" > 1.
I am able to iterate through the array and achieve that. however, it returns nil instead of the digits < 9.
def over_ten_sum
#splits the numbers over 10 into seperate digit and sums them
square_odd.map do |num|
if num > 9
num.to_s.chars.each_slice(2).map { |num_1, num_2| num_1.to_i + num_2.to_i }
end
end
end
With a value of [6, 4, 10, 2, 14, 7, 8, 4, 6, 7, 18, 4] it returns:
=> [nil, nil, [1], nil, [5], nil, nil, nil, nil, nil, [9], nil]
I am trying to have the output be
[6, 4, 1, 2, 5, 7, 8, 6, 7, 9, 4]
Just not seeing the disconnect here. Thank you in advance for any insights.
Suppose you were to write
[1, 2, 3].map { |n| }
#=> [nil, nil, nil]
An array of nils is returned because map returns nil for n if n is not assigned a value in the block. Similarly,
[1, 2, 3].map { |n| 2*n if n > 1 }
#=> [nil, 4, 6]
which is very similar to the problem with the OP's code. If one doesn't want nils in the array returned one simply needs to map each element of the array into a non-nil value:
[1, 2, 3].map { |n| n > 1 ? 2*n : n }
#=> [1, 4, 6]
Now let's look at the line
num.to_s.chars.each_slice(2).map { |num_1, num_2| num_1.to_i + num_2.to_i }
If num = 34, this returns [7], which, except for the fact that 7 is in an array, is correct. On the other hand, if num = 134 the expression returns [4, 4] (i.e., [1+3, 4]), which I don't expect is what is wanted. If, however, the numbers always have two digits, the above expression is the same as:
num[0].to_i + num[1].to_i
which is much simpler.1 To make it more general you need to write something like the following2:
def over_nine_sum(arr)
arr.map { |n| n > 9 ? n.to_s.each_char.reduce(0) { |t,s| t + s.to_i } : n }
end
over_nine_sum [12, 5, 71, 3]
#=> [3, 5, 8, 3]
See Enumerable#reduce (aka inject).
#JörgWMittag noted (see comment) that the sum of the digits of a single-digit number (0-9) is the same as the number itself, so there is no need to treat those numbers differently. We may therefore write
def sum_digits(arr)
arr.map { |n| n.to_s.each_char.reduce(0) { |t,s| t + s.to_i } }
end
sum_digits [12, 5, 71, 3]
#=> [3, 5, 8, 3]
As #steenslag's suggested in a comment, this can be simplified to
def sum_digits(arr)
arr.map { |n| n.digits.sum }
end
which uses the methods Integer#digits and Array#sum (both new in Ruby v2.4).
Consider the steps (for the first version of sum_digits above) when n = 34:
n.to_s.each_char.reduce(0) { |t,s| t + s.to_i }
#=> 34.to_s.each_char.reduce(0) { |t,s| t + s.to_i }
#=> "34".each_char.reduce(0) { |t,s| t + s.to_i }
Now reduce initializes the block variable t (the "memo", which is returned) to zero and passes the first digit of "34" to the block and assigns it to the block variable s:
t = 0
s = "3"
The block calculation is:
t + s.to_i
#=> 0 + "3".to_i
#=> 3
which is the updated value of t. Next,
s = "4"
t + s.to_i
#=> 3 + "4".to_i
#=> 3 + 4
#=> 7
1. Another problem is that if square_odd is a local variable, Ruby will raise an "undefined variable or method" exception when it evaluates it.
2. n.to_s.each_char.reduce(0)... is preferable to n.to_s.chars.reduce(0)... because chars returns a temporary array whereas each_char returns an enumerator.
Remove the if:
def over_ten_sum
#splits the numbers over 10 into seperate digit and sums them
square_odd.map do |num|
num.to_s.chars.each_slice(2).map { |num_1, num_2| num_1.to_i + num_2.to_i }
end.flatten
end
#=> [6, 4, 1, 2, 5, 7, 8, 4, 6, 7, 9, 4]
What was wrong? if num > 9 left out every other number from being treated and nothing was returned, so you got nil each time. To make it clearer, check the following code:
def over_ten_sum
#splits the numbers over 10 into seperate digit and sums them
square_odd.map do |num|
if num > 9
num.to_s.chars.each_slice(2).map { |num_1, num_2| num_1.to_i + num_2.to_i }
else
num
end
end.flatten
end
#=> [6, 4, 1, 2, 5, 7, 8, 4, 6, 7, 9, 4]
As you can see, the result is the same, because else send num back as it is when it is not greater than 9.

Find all integers between m and n whose sum of squared divisors is itself a square

Problem Question
Divisors of 42 are : 1, 2, 3, 6, 7, 14, 21, 42. These divisors squared are: 1, 4, 9, 36, 49, 196, 441, 1764. The sum of the squared divisors is 2500 which is 50 * 50, a square!
Given two integers m, n (1 <= m <= n) we want to find all integers between m and n whose sum of squared divisors is itself a square. 42 is such a number.
The result will be an array of arrays, each subarray having two elements, first the number whose squared divisors is a square and then the sum of the squared divisors.
Code below
How can I make this specific program run faster? My current code times out after n > 9999.
#returns the divisors of each number in an array of arrays
r = (m..n).to_a.map { |z| (1..z).select { |x| z % x == 0} }
#this finds all integers between m and n whose sum of squared divisors is itself a square
squarenumbers = r.map { |x| x.map { |c| c**2 }.inject(:+) }.select { |x| Math.sqrt(x) % 1 == 0 }
#returns an array of booleans.
booleans = r.map { |x| x.map { |c| c**2 }.inject(:+) }.map { |x| Math.sqrt(x) % 1 == 0 }
#returns the index of each of the true values in booleans as an array
indexer = booleans.map.with_index{|x, i| i if x == true }.compact
#returns the numbers whose squared divisors is a square in an array
unsqr = indexer.map { |x| (m..n).to_a[x] }
#merges the two arrays together, element for element and creates an array of arrays
unsqr.zip(squarenumbers)
# for m = 1 and n = 1000 the result would be
# [[1, 1], [42, 2500], [246, 84100], [287, 84100], [728, 722500]]
Brute-force calculatioins of factors
You begin by calculating:
m, n = 40, 42
r = (m..n).to_a.map { |z| (1..z).select { |x| z % x == 0} }
#=> [[1, 2, 4, 5, 8, 10, 20, 40], [1, 41], [1, 2, 3, 6, 7, 14, 21, 42]]
That's OK, but you don't need .to_a:
r = (m..n).map { |z| (1..z).select { |x| z % x == 0} }
#=> [[1, 2, 4, 5, 8, 10, 20, 40], [1, 41], [1, 2, 3, 6, 7, 14, 21, 42]]
This avoids an extra step, which is the creation of the temporary array1,2:
(m..n).to_a #=> [40, 41, 42]
Structure of a solution
Let's work backwards to come up with our code. First, concentrate on determining, for any given number q, if the sum of squares of the factors of q is itself a perfect square. Suppose we construct a 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 compute:
(m..n).select { |q| magic_number?(q) }
to return an array of all numbers between m and n that satisfy the property. magic_number? can be written like this:
def magic_number?(q)
return true if q == 1
s = sum_of_squared_factors(q)
s == Math.sqrt(s).round**2
end
Calculating sum of squared factors
So now we are left with writing the method sum_of_squared_factors. We can use your code to obtain the factors:
def factors(q)
(1..q).select { |x| q % x == 0 }
end
factors(40) #=> [1, 2, 4, 5, 8, 10, 20, 40]
factors(41) #=> [1, 41]
factors(42) #=> [1, 2, 3, 6, 7, 14, 21, 42]
and then write:
def sum_of_squared_factors(q)
factors(q).reduce(0) { |t,i| t + i*i }
end
sum_of_squared_factors(40) #=> 2210
sum_of_squared_factors(41) #=> 1682
sum_of_squared_factors(42) #=> 2500
Speeding the calculation of factors
There's something more we can do to speed up the calculation of factors. If f is a factor of n, f and n/f, are both factors of n. (For example, since 3 is a factor of 42, so is 42/3 #=> 14). We therefore need only obtain the smaller of each pair.
There is one exception to this rule. If n is a perfect square and f == n**0.5, then f = n/f, so we only include f among the factors of n (not n/f as well).
If turns out that if f is the smaller of the pair, f <=(n**0.5).round3. We therefore need only check to see which of the numbers (1..(n**0.5).round) are factors and include their complements (unless n is a perfect square, in which case we do not double-count (n**0.5).round):
q = 42
arr = (1..Math.sqrt(q).round).select { |x| q % x == 0 }
#=> [1, 2, 3, 6]
arr = arr.flat_map { |n| [n, q/n] }
#=> [1, 42, 2, 21, 3, 14, 6, 7]
arr.pop if a[-2] == a[-1]
arr
#=> [1, 42, 2, 21, 3, 14, 6, 7]
q = 36
arr = (1..Math.sqrt(q).round).select { |x| q % x == 0 }
#=> [1, 2, 3, 4, 6]
arr = arr.flat_map { |n| [n, q/n] }
#=> [1, 36, 2, 18, 3, 12, 4, 9, 6, 6]
arr.pop if a[-2] == a[-1]
#=> 6
arr
#=> [1, 36, 2, 18, 3, 12, 4, 9, 6]
so 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 out arr ("chaining" expressions), we obtain 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)
#=> [1, 42, 2, 21, 3, 14, 6, 7]
factors(36)
#=> [1, 36, 2, 18, 3, 12, 4, 9, 6]
See Enumerable#flat_map and Object#tap. (There's no need for this array to be sorted. In applications where it needs to be sorted, just tack .sort onto the end of flat_maps block.)
Wrapping up
In sum, we are left with 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) }
#=> `[1, 42, 246, 287, 728]
This calculation was completed in a blink of an eye.
Compute primes to further speed calculation of factors
Lastly, let me describe an even faster way to compute the factors of a number, using the method Prime::prime_division. That method decomposes any number into its prime components. Consider, for example, n = 360.
require 'prime'
Prime.prime_division(360)
#=> [[2, 3], [3, 2], [5, 1]]
This tells us that:
360 == 2**3 * 3**2 * 5**1
#=> true
It also tells us that every factor of 360 is the product of between 0 and 3 2's, multiplied by between 0 and 2 3's, multiplied by 0 or 1 5's. Therefore:
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
#=> [ 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 18,
# 20, 24, 30, 36, 40, 45, 60, 72, 90, 120, 180, 360]
We can check that:
a == (1..360).select { |n| (360 % n).zero? }
#=> true
One other check:
factors(40).sort
#=> [1, 2, 4, 5, 8, 10, 20, 40]
1. You could instead write that [*m..n] #=> [40, 41, 42].
2. Why is it not necessary to convert the range to an array? Enumerable#map, being an instance method of the module Enumerable, is available for use by every class that includes Enumerable. Array is one, but (m..n).class #=> Range is another. (See the second paragraph at Range).
3. Suppose f is smaller than n/f and f > n**0.5, then n/f < n/(n**0.5) = n**0.5 < f, a contradiction.
I don't know Ruby but the problem lies with the algorithm used in finding the divisors of a number (which is not specific to the language used, i.e. Ruby in this case).
r = (m..n).to_a.map { |z| (1..z).select { |x| z % x == 0} }
To find the divisors of an integer n you are dividing n by all positive integers unto n - 1 which means the loop runs n - 1 times. However, it is enough to divide upto sort(n) to calculate the divisors. In pseudocode this looks like below:
for i = 1 to i <= sqrt(n)
r = n % i
if r == 0 then
i is a divisor
if n / i != i then
n / i is another divisor
For example:
sqrt_42 = 6.48074069840786
i = 1 => 1 and 42 are two divisors
i = 2 => 2 and 21
i = 3 => 3 and 14
i = 4 => no divisor
i = 5 => no divisor
i = 6 => 6 and 7
And thats all.
This will improve the performance a lot since now the loop runs only sort(n) times instead of n - 1 times which is a big difference for large n.

Resources