How to search within a two-dimensional array - ruby

I'm trying to learn how to search within a two-dimensional array; for example:
array = [[1,1], [1,2], [1,3], [2,1], [2,4], [2,5]]
I want to know how to search within the array for the arrays that are of the form [1, y] and then show what the other y numbers are: [1, 2, 3].
If anyone can help me understand how to search only with numbers (as a lot of the examples I found include strings or hashes) and even where to look for the right resources even, that would be helpful.

Ruby allows you to look into an element by using parentheses in the block argument. select and map only assign a single block argument, but you can look into the element:
array.select{|(x, y)| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|(x, y)| x == 1}.map{|(x, y)| y}
# => [1, 2, 3]
You can omit the parentheses that correspond to the entire expression between |...|:
array.select{|x, y| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|x, y| x == 1}.map{|x, y| y}
# => [1, 2, 3]
As a coding style, it is a custom to mark unused variables as _:
array.select{|x, _| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|x, _| x == 1}.map{|_, y| y}
# => [1, 2, 3]

You can use Array#select and Array#map methods:
array = [[1,1], [1,2], [1,3], [2,1], [2,4], [2,5]]
#=> [[1, 1], [1, 2], [1, 3], [2, 1], [2, 4], [2, 5]]
array.select { |el| el[0] == 1 }
#=> [[1, 1], [1, 2], [1, 3]]
array.select { |el| el[0] == 1 }.map {|el| el[1] }
#=> [1, 2, 3]
For more methods on arrays explore docs.

If you first select and then map you can use the grep function to to it all in one function:
p array.grep ->x{x[0]==1}, &:last #=> [1,2,3]

Another way of doing the same thing is to use Array#map together with Array#compact. This has the benefit of only requiring one block and a trivial operation, which makes it a bit easier to comprehend.
array.map { |a, b| a if b == 1 }
#=> [1, 2, 3, nil, nil, nil]
array.map { |a, b| a if b == 1 }.compact
#=> [1, 2, 3]

You can use each_with_object:
array.each_with_object([]) { |(x, y), a| a << y if x == 1 }
#=> [1, 2, 3]

Related

Ruby, remove super-arrays

If I have an array of arrays, A, and want to get rid of all arrays in A who also have a sub-array in A, how would I do that. In this context, array_1 is a sub-array of array_2 if array_1 - array_2 = []. In the case that multiple arrays are simply rearranged versions of the same elements, bonus points if you can get rid of all but one of them, but you can handle this however you want if it's easier.
In python, I could easily use comprehension, with A being a set of frozen sets :
A = {a for a in A if all(b-a for b in A-{a})}
Is there a simple way to write this in ruby? I don't care if the order of A or it's arrays are preserved at all. Also, in my program, none of the arrays have duplicate elements, if that makes things any easier/faster.
Example
A = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
# [1,6] is a subarray of [1,3,6], so [1,3,6] should be removed
remove_super_arrays(A)
> A = [[1,6],[1,2],[2,4],[3,5],[2,3,6]]
A = [[1,2,4],[2,3,4],[1,4,5],[2,6]]
# although there is overlap, there are no subarrays, so nothing should be removed
remove_super_arrays(A)
> A = [[1,2,4],[2,3,4],[1,4,5],[2,6]]
A = [[1],[2,1,3],[2,4],[1,4]]
# [1] is a subarray of [2,1,3] and [1,4]
remove_super_arrays(A)
> A = [[1],[2,4]]
Code
def remove_super_arrays(arr)
order = arr.each_with_index.to_a.to_h
arr.sort_by(&:size).reject.with_index do |a,i|
arr[0,i].any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end.sort_by { |a| order[a] }
end
Examples
remove_super_arrays([[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]] )
#=> [[1,6],[1,2],[2,4],[3,5],[2,3,6]]
remove_super_arrays([[1,2,4],[2,3,4],[1,4,5],[2,6]])
#=> [[1,2,4],[2,3,4],[1,4,5],[2,6]]
remove_super_arrays([[1],[2,1,3],[2,4],[1,4]])
#=> [[1],[2,4]]
Explanation
Consider the first example.
arr = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
We first save the positions of the elements of a
order = arr.each_with_index.to_a.to_h # save original order
#=> {[1, 6]=>0, [1, 2]=>1, [2, 4]=>2, [3, 5]=>3, [1, 3, 6]=>4, [2, 3, 6]=>5}
Then reject elements of arr:
b = arr.sort_by(&:size)
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [1, 3, 6], [2, 3, 6]]
c = b.reject.with_index do |a,i|
arr[0,i].any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]
Lastly, reorder c to correspond to the original ordering of the elements of arr.
c.sort_by { |a| order[a] }
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]
which in this case happens to be the same order as the elements of c.
Let's look more carefully at the calculation of c:
enum1 = b.reject
#=> #<Enumerator: [[1, 6], [1, 2], [2, 4], [3, 5], [1, 3, 6],
# [2, 3, 6]]:reject>
enum2 = enum1.with_index
#=> #<Enumerator: #<Enumerator: [[1, 6], [1, 2], [2, 4], [3, 5],
# [1, 3, 6], [2, 3, 6]]:reject>:with_index>
The first element is generated by the enumerator enum2 and passed to the block and assigned as values of the block variables:
a, i = enum2.next
#=> [[1, 6], 0]
a #=> [1, 6]
i #=> 0
The block calculation is then performed:
d = arr[0,i]
#=> []
d.any? { |aa| (aa.size < a.size) && (aa-a).empty? }
#=> false
so a[0] is not rejected. The next pair passed to the block by enum2 is [[1, 2], 1]. That value is retained as well, but let's skip ahead to the last element passed to the block by enum2:
a, i = enum2.next
#=> [[1, 2], 1]
a, i = enum2.next
#=> [[2, 4], 2]
a, i = enum2.next
#=> [[3, 5], 3]
a, i = enum2.next
#=> [[1, 3, 6], 4]
a #=> [1, 3, 6]
i #=> 4
Perform the block calculation:
d = arr[0,i]
#=> [[1, 6], [1, 2], [2, 4], [3, 5]]
d.any? { |aa| (aa.size < a.size) && (aa-a).empty? }
#=> true
As true is returned, a is rejected. In the last calculation the first element of d is passed to the block and the following calculation is performed:
aa = [1, 6]
(aa.size < a.size)
#=> 2 < 3 => true
(aa-a).empty?
#=> ([1, 6] - [1, 3, 6]).empty? => [].empty? => true
As true && true #=> true, a ([1, 3, 6]) is rejected.
Alternative calculation
The following is a closer match to the OP's Python equivalent, but less efficient:
def remove_super_arrays(arr)
arr.select do |a|
(arr-[a]).all? { |aa| aa.size > a.size || (aa-a).any? }
end
end
or
def remove_super_arrays(arr)
arr.reject do |a|
(arr-[a]).any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end
end
This was a nice exercise for me. I have used the logic from here.
My code iterates over each subarray (except the first), then there is the magic substraction using the first index, when it is empty the other array contained both numbers.
def remove_super_arrays(arr)
arr.each_with_index.with_object([]) do |(sub_array, index), result|
next if index == 0
result << sub_array unless (arr.first - sub_array).empty?
end.unshift(arr.first)
end
arr = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
p remove_super_arrays(arr)
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]

Why can't I use |a,b| instead of |(a,b)| in arr.map { |(a, b)| !b.nil? ? a + b : a }?

In the code below, arr is meant to be a two-dimensional array, such as [[1,2],[4,5]]. It computes the sum of the elements of the sub arrays. A subarray can have only one element, in which case the sum is just that one element.
def compute(arr)
return nil unless arr
arr.map { |(a, b)| !b.nil? ? a + b : a }
end
Why does the code have to be |(a, b)| instead of |a,b|?
What does (a,b) mean in Ruby?
You could use |a,b| too, it's nothing different from |(a,b)|.
You may also rewrite the code as below, which doesn't have the element number limit for the sub arrays:
arr.map { |a| a.inject{ |sum,x| sum + x } }
or even:
arr.map { |a| a.inject(:+) }
Both are equivalent if arr is an array:
arr = [[1, 2], [4, 5]]
arr.map { |a, b| [a, b] } #=> [[1, 2], [4, 5]]
arr.map { |(a, b)| [a, b] } #=> [[1, 2], [4, 5]]
This is because the block is called with a single argument at a time: the subarray. Something like:
yield [1, 2]
yield [4, 5]
This changes if more than one arguments is yielded. each_with_index for example, calls the block with two arguments: the item (i.e. the subarray) and its index. Something like:
yield [1, 2], 0
yield [4, 5], 1
The difference is obvious:
enum = [[1, 2], [4, 5]].each_with_index
enum.map { |a, b| [a, b] } #=> [[[1, 2], 0], [[4, 5], 1]]
enum.map { |(a, b)| [a, b] } #=> [[1, 2], [4, 5]]
Note that omitting parenthesis also allows you to set a default argument value:
arr = [[1, 2], [4]]
arr.map { |a, b = 0| a + b } #=> [3, 4]

Returning all maximum or minimum values that can be multiple

Enumerable#max_by and Enumerable#min_by return one of the relevant elements (presumably the first one) when there are multiple max/min elements in the receiver. For example, the following:
[1, 2, 3, 5].max_by{|e| e % 3}
returns only 2 (or only 5).
Instead, I want to return all max/min elements and in an array. In the example above, it would be [2, 5] (or [5, 2]). What is the best way to get this?
arr = [1, 2, 3, 5]
arr.group_by{|a| a % 3} # => {1=>[1], 2=>[2, 5], 0=>[3]}
arr.group_by{|a| a % 3}.max.last # => [2, 5]
arr=[1, 2, 3, 5, 7, 8]
mods=arr.map{|e| e%3}
find max
max=mods.max
indices = []
mods.each.with_index{|m, i| indices << i if m.eql?(max)}
arr.select.with_index{|a,i| indices.include?(i)}
find min
min = mods.min
indices = []
mods.each.with_index{|m, i| indices << i if m.eql?(min)}
arr.select.with_index{|a,i| indices.include?(i)}
Sorry for clumsy code, will try to make it short.
Answer by #Sergio Tulentsev is the best and efficient answer, found things to learn there. +1
This is the hash equivalent of #Serio's use of group_by.
arr = [1, 2, 3, 5]
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |e,h| h[e%3] << e }.max.last
#=> [2, 5]
The steps:
h = arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |e,h| h[e%3] << e }
#=> {1=>[1], 2=>[2, 5], 0=>[3]}
a = h.max
#=> [2, [2, 5]]
a.last
#=> [2, 5]

How do I add a cumulative sum to an array for only one value?

I have an array of arrays with x and y values:
[[some_date1, 1], [some_date2, 3], [some_date3, 5], [some_date4, 7]]
The result should only sum the y values (1, 3, 5, 7) so that the result is like this:
[[some_date1, 1], [some_date2, 4], [some_date3, 9], [some_date4, 16]]
How is this possible in Ruby?
Yes, this is possible in Ruby. You can use [map][1] and do something like this:
sum = 0
array.map {|x,y| [x, (sum+=y)]}
This is how it works. For the given the input:
array = ["one", 1], ["two", 2]
It will iterate through each of the elements in the array e.g.) the first element would be ["one", 1].
It will then take that element (which is an array itself) and assign the variable x to the first element in that array e.g.) "one" and y to the second e.g.) 1.
Finally, it will return an array with the result like this:
=> ["one", 1], ["two", 3]
You can use map:
a = [[:some_date1, 1], [:some_date2, 3], [:some_date3, 5], [:some_date4, 7]]
sum = 0
a.map { |f, v| [f, (sum = sum + v)]}
=> [[:some_date1, 1], [:some_date2, 4], [:some_date3, 9], [:some_date4, 16]]
Since sum will be nil in the first iteration it is necessary to call to_i on it.
a = [['some_date1', 1], ['some_date2', 3], ['some_date3', 5], ['some_date4', 7]]
a.each_cons(2){|a1, a2| a2[1] += a1[1]}
last = 0
arr.map do |a, b|
last = last + b
[a, last]
end
I'd use:
ary = [['some_date1', 1], ['some_date2', 3], ['some_date3', 5], ['some_date4', 7]]
ary.inject(0) { |m, a|
m += a[-1]
a[-1] = m
}
After running, ary is:
[["some_date1", 1], ["some_date2", 4], ["some_date3", 9], ["some_date4", 16]]
The reason I prefer this is it doesn't require the addition of an accumulator variable. inject returns a value but it gets thrown away without an assignment.

Number of arguments in a ruby block

I'm new to Ruby, but not to languages that allow lambda's, such as groovy. So I saw this example:
myArray.product(otherArray).reject{|i,j| i > j}
in a ruby code block, and I hadn't seen this block take 2 arguments before, but when I went to look at the documentation I can only see the documentation that says that it takes 1 argument. I looked at the same for the enumerable class, but that doc only shows 1 argument also.
I understand that it works, I guess I was hoping that there was an easier way to determine how many arguments it takes other then a guess and test method. How can I tell how many arguments a block takes in Ruby?
This works because Ruby supports destructuring.
Destructuring allows you to bind a set of variables to a corresponding set of values anywhere that you can normally bind a value to a single variable.
This allows the following to hold true:
arr = [1, 2]
x = arr
x == [1, 2] # true
y, z = arr
y == 1 # true
z == 2 # true
You can see from the following code that destructuring in arguments to blocks isn't unique to the built-in methods that take a block:
def my_method(arr)
yield arr
end
my_method([1, 2, 3]) {|x| puts x.inspect }
# => [1, 2, 3]
my_method([1, 2, 3]) {|x, y, z| puts x.inspect }
# => 1
Check out Destructuring with Ruby for more information.
You can do some interesting restructuring in block parameters, depending on the structure of your array:
[[1, 2], [3, 4], [5, 6], [7, 8]].reject {|x,y| y == 8 }
#=> [[1, 2], [3, 4], [5, 6]]
You can group them in parentheses:
[ [[1,2],3], [[1,3],6] ].select {|(x,y),z| x == 1 && z == 3 }
#=> [ [[1,2],3] ]
You can also use the splat operator for various things, like dealing with variable-length subarrays:
[[:a,:b,2,3,4,5,6], [:c,:d,7,8,9]].each {|x,y,*numbers| puts numbers.inspect }
#=> [2,3,4,5,6]
#=> [7,8,9]
Ruby is flexible in how it interprets the arguments; here is a similar example, with one and then two arguments:
[1, 3].product([2, 4]).reject {|a| a.first > a.last }
=> [[1, 2], [1, 4], [3, 4]]
[1, 3].product([2, 4]).reject {|a,b| a > b }
=> [[1, 2], [1, 4], [3, 4]]
The rule of thumb here is that you can treat the arguments either as a composite object, or as individual elements in a collection. E.g.,
[1, 2, 3].tap {|a,b,c| puts [a,b,c].inspect }
[1, 2, 3]
...
[1, 2, 3].tap {|a,b| puts [a,b].inspect }
[1, 2]
...
[1, 2, 3].tap {|a| puts a.inspect }
[1, 2, 3]

Resources