Sorting array by more than one condition - arrays

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

Related

How can I arrange k elements by frequency in Decreasing Order in Ruby?

Given the array (array) [1, 1, 2, 2, 2, 3] this method should return (new_array)
[2, 2, 2, 1, 1, 3]
Heres what I have tried so far
Converted array into hash
Key being the element and value being the count
How do I recreate the array again to match new_array?
Here's one way:
array = [1,1,2,2,2,3]
array.tally # This is the bit you did already. Note that this uses the new ruby 2.7 method. You get: {1=>2, 2=>3, 3=>1}
.sort_by {|k, v| -v} # Now we have: [[2, 3], [1, 2], [3, 1]]
.flat_map { |element, count| Array.new(count, element) }
# And that gives the final desired result of:
[2, 2, 2, 1, 1, 3]
Or another variant, along the same lines:
array.tally # {1=>2, 2=>3, 3=>1}
.invert # {2=>1, 3=>2, 1=>3}
.sort # [[1, 3], [2, 1], [3, 2]]
.reverse # [[3, 2], [2, 1], [1, 3]]
.flat_map { |element, count| [element] * count }
Or, here's something completely different:
array.sort_by { |x| -array.count(x) }
Here is another one:
array = [1,1,2,2,2,3]
p array.group_by(&:itself).values.sort_by(&:size).flatten
def sort_chunks_by_length(arr)
arr.slice_when(&:!=).sort_by { |a| -a.size }.flatten
end
sort_chunks_by_length [1,1,2,2,2,3]
#=> [2, 2, 2, 1, 1, 3]
sort_chunks_by_length [1,1,2,2,2,1,3,3]
#=> [2, 2, 2, 1, 1, 3, 3, 1]
I have assumed that for the second example the desired return value is as shown, as opposed to:
#=> [2, 2, 2, 1, 1, 1, 3, 3]
The steps for that example are as follows.
arr = [1,1,2,2,2,1,3,3]
enum = arr.slice_when(&:!=)
#=> #<Enumerator: #<Enumerator::Generator:0x00007ffd1a9740b8>:each>
This is shorthand for:
enum = arr.slice_when { |x,y| x!=y }
We can see the elements that will be generated by this enumerator by converting it to an array:
enum.to_a
#=> [[1, 1], [2, 2, 2], [1], [3, 3]]
Continuing,
a = enum.sort_by { |a| -a.size }
#=> [[2, 2, 2], [1, 1], [3, 3], [1]]
a.flatten
#=> [2, 2, 2, 1, 1, 3, 3, 1]
The operative line could be replaced by either of the following.
arr.chunk(&:itself).map(&:last).sort_by { |a| -a.size }.flatten
arr.chunk_while(&:==).sort_by { |a| -a.size }.flatten
See Enumerable#slice_when, Enumerable#sort_by, Enumerable#chunk and Enumerable#chunk_while.

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

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

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

Resources