Joining two ranges into 2d array Ruby - arrays

How do I join two ranges into a 2d array as such in ruby? Using zip doesn't provide the result I need.
(0..2) and (0..2)
# should become => [[0,0],[0,1],[0,2], [1,0],[1,1],[1,2], [2,0],[2,1],[2,2]]

Ruby has a built in method for this: repeated_permutation.
(0..2).to_a.repeated_permutation(2).to_a

I'm puzzled. Here it is a day after the question was posted and nobody has suggested the obvious: Array#product:
[*0..2].product [*1..3]
#=> [[0, 1], [0, 2], [0, 3], [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3]]

range_a = (0..2)
range_b = (5..8)
def custom_join(a, b)
a.inject([]){|carry, a_val| carry += b.collect{|b_val| [a_val, b_val]}}
end
p custom_join(range_a, range_b)
Output:
[[0, 5], [0, 6], [0, 7], [0, 8], [1, 5], [1, 6], [1, 7], [1, 8], [2, 5], [2, 6], [2, 7], [2, 8]]

straight forward solution:
range_a = (0..2)
range_b = (5..8)
def custom_join(a, b)
[].tap{|result| a.map{|i| b.map{|j| result << [i, j]; } } }
end
p custom_join(range_a, range_b)
Output:
[[0, 5], [0, 6], [0, 7], [0, 8], [1, 5], [1, 6], [1, 7], [1, 8], [2, 5], [2, 6], [2, 7], [2, 8]]

Simply, this will do it:
a = (0...2).to_a
b = (0..2).to_a
result = []
a.each { |ae| b.each { |be| result << [ae, be] } }
p result
# => [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]]

Related

Optimised loop for array using ruby

I have (m = rows-1, n = cols-1) dimensional matrix.
And I passing i to method which will return array in following manner (provided with i <= m,n)
Suppose n=0, so for 4x4 matrix, it will return boundary elements position.
Do not consider below as ruby syntax, get only flow.
square = [[i,i] -> [i, m-i] -> [n-i, m-1] -> [n-i, i] -> [i,i]]
(no data is repeated in above)
I achieved above in recursion manner by setting parameters but I need easier/optimised trick.
Update - for user sawa
arr = [*1..16].each_slice(4).to_a
m,n = arr.length-1, arr[0].length-1
loop_count = 0
output = [[0, 0], [1, 0], [2, 0], [3, 0], [4, 0], [4, 1], [4, 2], [3, 2], [2, 2], [1, 2], [0, 2], [0, 1]]
loop_count = 1
output = [[1, 1], [2, 1], [2, 2], [1, 2]]
I ended up with this solution, but I think there is a better way out there.
First define a method to print the matrix mapped by indexes, just to check if the result id correct:
def print_matrix(n,m)
range_n, range_m = (0..n-1), (0..m-1)
mapp = range_m.map { |y| range_n.map { |x| [x, y] } }
mapp.each { |e| p e }
puts "-" * 8 * n
end
Then define a method that returns the frame starting from the loop s (where 0 is the external frame):
def frame (n, m, s = 0)
res = []
return res if (s >= n/2 and s >= m/2) and (n.even? or m.even?)
(s..n-s-1).each { |x| res << [x,s] }
(s..m-s-1).each { |y| res << [res.last[0], y] }
(res.last[0].downto s).each { |x| res << [x, res.last[1]] }
(res.last[1].downto s).each { |y| res << [res.last[0], y] }
res.uniq
end
Now, call the methods and check the output:
n, m, loops = 4, 4, 1
print_matrix(n,m)
frame(n, m, loops)
# [[0, 0], [1, 0], [2, 0], [3, 0]]
# [[0, 1], [1, 1], [2, 1], [3, 1]]
# [[0, 2], [1, 2], [2, 2], [3, 2]]
# [[0, 3], [1, 3], [2, 3], [3, 3]]
# --------------------------------
# [[1, 1], [2, 1], [2, 2], [1, 2]]
Here we can use Matrix methods to advantage, specifically Matrix::build, Matrix#minor and Matrix#[].
Code
require 'matrix'
def border_indices(nrows, ncols, i)
m = Matrix.build(nrows, ncols) { |r,c| [r,c] }.minor(i..nrows-1-i, i..ncols-1-i)
[[1,0,m.row_count-1], [0,1,m.column_count-1],
[-1,0,m.row_count-1], [0,-1,m.column_count-2]].
each_with_object([[0,0]]) do |(x,y,n),a|
n.times { a << [a.last.first+x, a.last.last+y] }
end.map { |i,j| m[i,j] }
end
Examples
nrows = 5
ncols = 6
border_indices(nrows, ncols, 0)
#=> [[0, 0], [1, 0], [2, 0], [3, 0],
# [4, 0], [4, 1], [4, 2], [4, 3], [4, 4],
# [4, 5], [3, 5], [2, 5], [1, 5],
# [0, 5], [0, 4], [0, 3], [0, 2], [0, 1]]
border_indices(nrows, ncols, 1)
#=> [[1, 1], [2, 1],
# [3, 1], [3, 2], [3, 3],
# [3, 4], [2, 4],
# [1, 4], [1, 3], [1, 2]]
border_indices(nrows, ncols, 2)
#=> [[2, 2], [2, 3]]
Explanation
Consider the calculation of border_indices(5, 6, 1).
nrows = 5
ncols = 6
i = 1
mat = Matrix.build(nrows, ncols) { |r,c| [r,c] }
#=> Matrix[[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5]],
# [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4], [1, 5]],
# [[2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5]],
# [[3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5]],
# [[4, 0], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5]]]
m = mat.minor(i..nrows-1-i, i..ncols-1-i)
#=> mat.minor(1..3, 1..4)
#=> Matrix[[[1, 1], [1, 2], [1, 3], [1, 4]],
# [[2, 1], [2, 2], [2, 3], [2, 4]],
# [[3, 1], [3, 2], [3, 3], [3, 4]]]
b = [[1,0,m.row_count-1], [0,1,m.column_count-1],
[-1,0,m.row_count-1], [0,-1,m.column_count-2]]
#=> [[1, 0, 2], [0, 1, 3], [-1, 0, 2], [0, -1, 2]]
c = b.each_with_object([[0,0]]) do |(x,y,n),a|
n.times { a << [a.last.first+x, a.last.last+y] }
end
#=> [[0, 0], [1, 0],
# [2, 0], [2, 1], [2, 2],
# [2, 3], [1, 3],
# [0, 3], [0, 2], [0, 1]]
c.map { |i,j| m[i,j] }
#=> [[1, 1], [2, 1],
# [3, 1], [3, 2], [3, 3],
# [3, 4], [2, 4],
# [1, 4], [1, 3], [1, 2]]
Note that in the calculation of c, a.last is the last pair of indices added to the array being constructed (a.last = [a.last.first, a.last.last]).
Following will work for both m == n & m != n case.
I hope, all will consider what matrix variable below stands for (2 D array)
def matrixRotation(matrix)
m,n = matrix.length-1, matrix[0].length-1
loop_count = [m,n].min/2
0.upto(loop_count) do |i|
indices = []
i.upto(m-i) { |j| indices << [j, i] }
i.upto(n-i) { |j| indices << [m-i, j] }
i.upto(m-i) { |j| indices << [m-j, n-i] }
i.upto(n-i) { |j| indices << [i, n-j] }
puts "-------------- For Layer #{i+1} ---------------", nil
indices = indices.uniq
values = indices.map { |x| matrix[x[0]][x[1]] }
puts 'indices:', indices.inspect, nil, 'values:', values.inspect
end
end

How to zip every element of array to every element of another array in ruby?

Say I have this array:
[0, 1, 4], [2, 3]
How can I merge them to get:
[0,2], [0,3], [1,2], [1,3], [4,2], [4,3]
I tried:
[0, 1, 4].zip [2, 3]
But I got:
[[0, 2], [1, 3], [4, nil]]
Any ideas?
[0, 1, 4].product([2, 3])
That should generate:
[[0, 2], [0, 3], [1, 2], [1, 3], [4, 2], [4, 3]]

Summing elements from arrays with matching elements

I have an array that looks like this:
original = [[1, 2, 3], [2, 2, 2], [1, 2, 3], [2, 2, 2], [2, 2, 3], [1, 2, 2], [5, 4, 2]]
I'd like to get a new array whose elements that match the second and third position would sum up its first position to get this:
expected_output = [[4, 2, 3], [5, 2, 2], [5, 4, 2]]
I got to grouping the elements from the array as follows:
new_array = original.group_by {|n| n[1] && n[2] }
# => {3=>[[1, 2, 3], [1, 2, 3], [2, 2, 3]], 2=>[[2, 2, 2], [2, 2, 2], [1, 2, 2], [5, 4, 2]]}
It is still far from my desired output.
Here's one way to return a new array of arrays where the first element of each array is the sum of the original array's first element where its second and third elements match:
arr = [[1, 2, 3], [2, 2, 2], [1, 2, 3], [2, 2, 2], [2, 2, 3], [1, 2, 2], [5, 4, 2]]
array_groups = arr.group_by { |sub_arr| sub_arr[1, 2] }
result = array_groups.map do |k, v|
k.unshift(v.map(&:first).inject(:+))
end
result
# => [[4, 2, 3], [5, 2, 2], [5, 4, 2]]
Hope this helps!
This will produce a similar result using an array grouping rather than combining the two latter numbers.
original = [[1, 2, 3], [2, 2, 2], [1, 2, 3], [2, 2, 2], [2, 2, 3], [1, 2, 2], [5, 4, 2]]
new = original.group_by {|n| [n[1], n[2]] }
added = new.map{|x| [new[x.first].map(&:first).inject(0, :+),x.first].flatten}
puts added.to_s
original.each_with_object(Hash.new(0)) { |(f,*rest),h| h[rest] += f }.
map { |(s,t),f| [f,s,t] }
# => [[4, 2, 3], [5, 2, 2], [5, 4, 2]]
Note that
original.each_with_object(Hash.new(0)) { |(f,*rest),h| h[rest] += f }
#=> {[2, 3]=>4, [2, 2]=>5, [4, 2]=>5}
Hash.new(0) is sometimes called a counting hash. To understand how that works, see Hash::new, especially the explanation of the effect of providing a default value as an argument of new. In brief, if a hash is defined h = Hash.new(0), then if h does not have a key k, h[k] returns the default value, here 0 (and the hash is not changed).

How to convert multidimensional array into a 2 dimensional array?

Having the following nested array
[[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5]], [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4], [1, 5]], [[2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5]], [[3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5]], [[4, 0], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5]], [[5, 0], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5]]]
I'd like to remove subarray containers until it becomes a 2 dimensional array like:
[[0,0], [5,1], [5,4]...]
.flatten removes everything and I need to keep the groups of 2 within subarrays.
also, next time you can try to read documentation :)
a = [[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5]], [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4], [1, 5]], [[2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5]], [[3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5]], [[4, 0], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5]], [[5, 0], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5]]]
a.flatten(1)
>[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [1, 0], [1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [4, 0], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [5, 0], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5]]

Combinaison ruby Array multidimensionnel to get a Array two dimensional

I have a Array multidimensionnel like:
[[1, 1, 4], [2],[2, 3]]
How to get a combinaison each element except the combinaison in the same array: [1, 1],[1, 4],[2, 3]
I want to get:
[1, 2],[1, 3],[4, 2],[4, 3],[2, 3]
Thanks.
Short answer is:
[[1, 1, 4], [2],[2, 3]].combination(2).flat_map {|x,y| x.product(y)}.uniq
# => [[1, 2], [4, 2], [1, 3], [4, 3], [2, 2], [2, 3]]
Step by step
step1 = [[1, 1, 4], [2],[2, 3]].combination(2)
# => [[[1, 1, 4], [2]], [[1, 1, 4], [2, 3]], [[2], [2, 3]]]
step2 = step1.flat_map {|x,y| x.product(y)}
# => [[1, 2], [1, 2], [4, 2], [1, 2], [1, 3], [1, 2], [1, 3], [4, 2], [4, 3], [2, 2], [2, 3]]
result = step2.uniq
# => [[1, 2], [4, 2], [1, 3], [4, 3], [2, 2], [2, 3]]
Update
For full uniqueness you could use:
[[1, 1, 4], [2],[2, 3, 4]].combination(2).flat_map {|x,y| x.product(y)}.map(&:sort).uniq
arr = [[1, 1, 4], [2], [2, 3]]
a = arr.map(&:uniq)
(arr.size-1).times.flat_map { |i| arr[i].product(arr[i+1..-1].flatten.uniq)}.uniq
#=> [[1,2],[1,3],[4,2],[4,3],[2,2],[2,3]]
Here's another way that uses the method Array#difference that I defined here:
arr.flatten.combination(2).to_a.difference(arr.flat_map { |a| a.combination(2).to_a }).uniq
Array#difference is similar to Array#-. The difference is illustrated in the following example:
a = [1,2,3,4,3,2,2,4]
b = [2,3,4,4,4]
a - b #=> [1]
a.difference b #=> [1, 3, 2, 2]

Resources