Optimised loop for array using ruby - arrays

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

Related

Why is my enumeration stopping after first rejection in Ruby?

Desperate need of help. I am trying to remove arrays from and array of arrays, and I have hit a road block. Essentially, if the first value in the child-array doesn't exist in either position of any other child-arrays, then it should be deleted. (presume that the array will be sorted - cause it will be)
arr = [[0, 1], [2, 3], [4, 5]]
arr.each_with_index do |inside_array, index|
if !index.zero?
# arr.delete(arr[index]) if arr.select {|x| x.include?(inside_array[0])}.count < 2
# refactored
arr.reject! {|x| x.include?(inside_array[0])}
end
end
=> [[0, 1], [4, 5]]
# Why does it stop itterating/enumerating after the first deletion?
# Goal output is [[0, 1]] for this example
Similarly, an array such as [[0, 1], [2, 3], [1, 5]], should yield [[0, 1], [1, 5]]
-or -
[[0, 1], [2, 3], [0, 3]], should yield [[0, 1], [0, 3]]
You've tried to modify origin array. That's your problem.
In that cases you need to duplicate it like this:
arr = [[0, 1], [2, 3], [4, 5]]
arr.dup.each_with_index do |inside_array, index|
if !index.zero?
arr.reject! {|x| x.include?(inside_array[0])}
end
end
arr #=> [[0, 1]]
So just use dup
As for the second question (implementation of subarray removal), I suggest this refactoring:
def remove_subarray(arr)
arr.reject { |inside_array| (inside_array & arr.first).empty? }
end
remove_subarray([[0, 1], [2, 3], [4, 5]]) #=> [[0, 1]]
remove_subarray([[0, 1], [2, 3], [1, 5]]) #=> [[0, 1], [1, 5]]
remove_subarray([[0, 1], [2, 3], [0, 3]]) #=> [[0, 1], [0, 3]]

How to count number or rectangles in a 2D array with Ruby?

Suppose I have an array. I wish to identify a rectangles top-left and bottom-right point. This is easy with a single rectangle. See my implementation below.
The problem becomes challenging when there are multiple rectangles. How do you identify top-left and bottom-right points with multiple rectangles?
rules continuous 0's are rectangles: In the below example there are 3 rectangles.
array = [
[1,1,1,1,0,0],
[1,0,1,1,0,0],
[1,0,1,1,0,0],
[1,1,0,0,1,1],
[1,1,0,0,1,1]]
def parse_array(arr)
answer = []
arr.each_with_index do |sub_array, x|
sub_array.each_with_index do |number, y|
if number == 0
answer.push([x,y])
end
end
end
answer
end
def edges(arr)
[arr.first, arr.last]
end
def get_length_and_width(arr)
width = (arr[1][1] - arr[0][1]) + 1
height = (arr[1][0] - arr[0][0]) + 1
[width, height]
end
Basic idea:
Deep-clone the array so we don't mess it up
Find a top-left corner of a rectangle
See how high it goes
See how wide it goes
Fill it up so we don't find those spaces any more
Repeat till no zeroes remain
So...
class RectFinder < Array
def initialize(array)
super()
#a = array.map { |x| x.dup }
#h = array.size
#w = array.first.size
find_rects
end
private def find_rects
(0...#h).each do |r|
(0...#w).each do |c|
if #a[r][c] == 0
self << find_rect(r, c)
end
end
end
end
private def find_rect(r, c)
w = ((c + 1)...#w).take_while { |cc| #a[r][cc] == 0 }.size + 1
h = ((r + 1)...#h).take_while { |rr| (c...(c + w)).all? { |cc| #a[rr][cc] == 0 } }.size + 1
(r...(r + h)).each { |rr| #a[rr][c...(c + w)] = [1] * w }
[[r, c], [r + h - 1, c + w - 1]]
end
end
p RectFinder.new(array)
# => [[[0, 4], [2, 5]], [[1, 1], [2, 1]], [[3, 2], [4, 3]]]
My understanding is that the question is, when a given array with n elements ("rows"), each element being an array of m elements ("columns") is viewed as a matrix, construct an array of all submatrices that contain only zeroes, where each submatrix is identified by the coordinates of its upper-left and lower-right elements. For example, the elements array[1][4], array[1][5], array[2][4] and array[2][5], which all equal zero, comprise a submatrix identified by the pair of coordinates [[1,4],[2,5]].
require 'matrix'
def all_zero_subarrays(arr)
m = Matrix[*arr]
last_row, last_col = arr.size-1, arr.first.size-1
(0..last_row).each_with_object([]) do |i,a|
(0..last_col).each do |j|
next unless arr[i][j].zero?
(i..last_row).each do |ii|
(j..last_col).each do |jj|
next unless arr[ii][jj].zero?
a << [[i,j], [ii,jj]] if m.minor((i..ii), (j..jj)).to_a.flatten.uniq == [0]
end
end
end
end
end
array = [
[1,1,1,1,0,0],
[1,0,1,1,0,0],
[1,0,1,1,0,0],
[1,1,0,0,1,1],
[1,1,0,1,1,1]]
I changed array[4][3] from 0 to 1, array having been defined in the question. My reason for doing so will be made clear later.
arr = all_zero_subarrays(array)
#=> [[[0, 4], [0, 4]], [[0, 4], [0, 5]], [[0, 4], [1, 4]], [[0, 4], [1, 5]],
# [[0, 4], [2, 4]], [[0, 4], [2, 5]], [[0, 5], [0, 5]], [[0, 5], [1, 5]],
# [[0, 5], [2, 5]],
# [[1, 1], [1, 1]], [[1, 1], [2, 1]], [[1, 4], [1, 4]], [[1, 4], [1, 5]],
# [[1, 4], [2, 4]], [[1, 4], [2, 5]], [[1, 5], [1, 5]], [[1, 5], [2, 5]],
# [[2, 1], [2, 1]], [[2, 4], [2, 4]], [[2, 4], [2, 5]], [[2, 5], [2, 5]],
# [[3, 2], [3, 2]], [[3, 2], [3, 3]], [[3, 2], [4, 2]], [[3, 3], [3, 3]],
# [[4, 2], [4, 2]]]
See Matrix::[] and Matrix#minor.
If desired, elements of arr that represent submatrices that are "contained" in another submatrix can be removed. For example, [[0, 4], [1, 5]] is "contained in [[0, 4], [2, 5]]. Here is a way of doing that.
def second_rect_in_first?(rect1, rect2)
ul1, br1 = rect1
ul2, br2 = rect2
first_ul_of_second?(ul1, ul2) && first_ul_of_second?(br2, br1)
end
def first_ul_of_second?((i1, j1), (i2, j2))
(i1 <= i2) && (j1 <= j2)
end
arr.each_with_object([]) do |rect, a|
next if a.any? { |arect| second_rect_in_first?(arect, rect) }
a.reject! { |arect| second_rect_in_first?(rect, arect) }
a << rect
end
#=> [[[0, 4], [2, 5]],
# [[1, 1], [2, 1]],
# [[3, 2], [3, 3]],
# [[3, 2], [4, 2]]]
Note that [[3, 2], [3, 3]] and [[3, 2], [4, 2]]] both include [3, 2].

Joining two ranges into 2d array Ruby

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]]

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]

Sorting array by more than one condition

I have an array of points:
arr = [[2,0], [1,0], [2,1], [1,1]]
How would I sort the elements in descending and ascending orders by x first and then by y values of the similar x value?
max = [[2,1], [2,0], [1,1], [1,0]]
min = [[1,0], [1,1], [2,0], [2,1]]
.
min = arr.sort
# => [[1, 0], [1, 1], [2, 0], [2, 1]]
max = min.reverse
# => [[2, 1], [2, 0], [1, 1], [1, 0]]
If performance is an issue rather than simplicity, then the following can be used.
min = arr.sort_by(&:itself)
This is a good use case for Enumerable#sort_by.
For max:
arr.sort_by { |el| [-el[0], -el[1]] }
=> [[2, 1], [2, 0], [1, 1], [1, 0]]
For min:
arr.sort_by { |el| [el[0], el[1]] }
=> [[1, 0], [1, 1], [2, 0], [2, 1]]

Resources