Using multiple conditions in one if-statement in Ruby Language - arrays

I write something like this in Ruby:
if a.max == a[0]
brand = b[0]
elsif a.max == a[1]
brand = b[1]
elsif a.max == a[2]
brand = b[2]
elsif a.max == a[3]
brand = b[3]
end
a and b both are unique arrays.
Is there any way to check all if and elsif's in the same condition?
Only one condition for a[0], a[1], a[2] and a[3]?

Array#index might help in cases like these (assuming the size of a and b is the same):
brand = b[a.index(a.max)]
In cases in which the array a might be empty, you will need an additional condition to avoid an error:
index = a.index(a.max)
brand = b[index] if index

Two more ways:
a = [3, 1, 6, 4]
b = [2, 8, 5, 7]
b[a.each_index.max_by { |i| a[i] }]
#=> 5
or
b[a.each_with_index.max_by(&:first).last]
#=> 5

Assuming a and b have the same size, e.g.
a = [2, 5, 8, 1]
b = [:a, :b, :c, :d]
you could combine zip and max:
a.zip(b).max.last # or more explicit: a.zip(b).max_by(&:first).last
#=> :c # or reversed: b.zip(a).max_by(&:last).first
or max_by and with_index:
b.max_by.with_index { |_, i| a[i] }
#=> :c

If your array has multiple maxima, you may want to get the indices of the array that correspond to all the maxima:
a = [10, 12, 12]
b = [:a, :b, :c]
# Compute and store the maximum once, to avoid re-computing it in the
# loops below:
a_max = a.max
idxs = a.each_with_index.select{ |el, idx| el == a_max }.map{ |el, idx| idx }
# or:
idxs = a.each_with_index.map{ |el, idx| idx if el == a_max }.compact
puts "#{idxs}"
# [1, 2]
puts "#{idxs.map{ |idx| b[idx] }}"
# [:b, :c]

Related

Ruby three-way transitive comparison

I want to compare three arrays transitively to see if there are any shared elements:
arrs = [
["AAA", "", ""],
["", "", "CCC"],
["AAA", "BBB", "CCC"]
]
I want to return a matrix that compares the elements transitively. That is, if two arrays share any of the same elements, or if they match with a third record, return 1. Otherwise return 0.
With this example the result should be:
result = [
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]
]
result[0][0] is 1 because if we compare arrs[0] with arrs[0] (compare itself), they share "AAA".
result[0][1] is 1 because if we compare arrs[0] and arrs[1] there are no shared elements, but both arrs[0] & arrs[2] and arrs[1] & arrs[2] return an intersecting element, so we return 1
result[0][2] is 1 because if we compare arrs[0] with arrs[2], they share "AAA"
We repeat the process for all other array combinations in arrs.
It's really not that tricky, you just need to double-map here:
def transitive(arr)
arr.map do |a|
arr.map do |b|
(a & b).any? ? 1 : 0
end
end
end
A more Ruby approach is to use true or false, but 1 and 0 is fine if you can handle the ternary to convert it.
How it works:
arrs = [
["AAA", "", ""],
["", "", "CCC"],
["AAA", "BBB", "CCC"]
]
transitive(arrs)
# => [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
Not a very exciting example. Here's one that has more variety:
arrs = [
%w[ A B C ],
%w[ A D E ],
%w[ D E F ]
]
transitive(arrs)
# => [[1, 1, 0], [1, 1, 1], [0, 1, 1]]
Where that has some misses.
Assumption
We are given an array arr of size n, each element being an array of size m. We wish to construct another n x m array, a, such each element a[i][j] equals 1 (else 0) if all of the following arrays are non-empty:
a[i] & a[i] # non-empty if a is non-empty
a[i] & a[(i+1)%n]
a[(i+1)%n] & a[(i+2)%n]
a[(j-1)%n] & a[j]
This is what I interpret "transitive" to mean. Note that I've assumed the transitive relation "wraps around" from the last to the first element of arr.
Let's consider an example.
arr = [["A", "B", "C"],
["A", "D", "E"],
["D", "E", "F"]]
Suppose we wish to compute a[i][j] of the array a being constructed. This equals 1 (else 0) if the following arrays are all non-empty:
a[1] & a[1] #=> a[1] => ["A", "D", "E"]
a[1] & a[2%3] #=> a[1] & a[2] => ["D"]
a[(i+1)%n] & a[(i+2)%n] #=> a[2] & a[1] => []
Note that had (a[1] & a[2%3]).empty? #=> true, it would not be necessary to compute the third expression (or any following expressions if arr were larger).
For i #=> 0,
a[0,0] = (arr[0] & arr[0]).any?
#=> arr[0].any? #=> true, hence = 1
a[0,1] = (arr[0] & arr[1]).any?
#=> ["A"].any? #=> true, hence = 1
a[0,2] = (arr[0] & arr[1]).any? && (arr[1] & arr[2]).any?
#=> (a[0,1] == 1) && ["D"].any? => true && true => true, hence = 1
For i #=> 1,
a[1,1] = (arr[1] & arr[1]).any?
#=> arr[1].any? #=> true, hence = 1
a[1,2] = (arr[1] & arr[2]).any?
#=> ["D"].any? #=> true, hence = 1
a[1,0] = (arr[1] & arr[2]).any? && (arr[2] & arr[0]).any?
#=> (a[1,2] == 1) && [].any? => true && false => true, hence = 0
For i #=> 2,
a[2,2] = (arr[2] & arr[2]).any?
#=> arr[2].any? #=> true, hence = 1
a[2,0] = (arr[2] & arr[0]).any?
#=> [].any? #=> false, hence = 0
a[2,1] = (arr[2] & arr[0]).any? && (arr[0] & arr[1]).any?
#=> (a[2,0] == 1) && ["A"].any? => false && true => false, hence = 0
Code
require 'set'
def transitive(arr)
n = arr.size
st = n.times.with_object(Set.new) do |i,st|
(i..i+n-1).each do |j|
if j==i
st << [i,j]
else
jj = j % n
jprev = (j-1) % n
break unless st.include?([i,jprev]) & (arr[jprev] & arr[jj]).any?
st << [i,jj]
end
end
end
Array.new(n) do |i|
Array.new(arr.first.size) { |j| st.include?([i,j]) ? 1 : 0 }
end
end
Example
For arr defined earlier,
transitive(arr)
#=> [[1, 1, 1],
# [0, 1, 1],
# [0, 0, 1]]
Explanation
The steps are as follows:
n = arr.size
#=> 3
st = n.times.with_object(Set.new) do |i,st|
(i..i+n-1).each do |j|
if j==i
st << [i,j]
else
jj = j % n
jprev = (j-1) % n
break unless st.include?([i,jprev]) & (arr[jprev] & arr[jj]).any?
st << [i,jj]
end
end
end
#=> #<Set: {[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]}>
st is a set of the transitive elements of arr. This shows that elements of arr with indices [0, 2] (order matters) are transitive, but those with indices [2, 0] are not (because st does not contain [2, 0]). Notice that once [2, 0] it was determined to not be transitive it was not necessary to check [2, 1].
The last step uses the method Array::new:
Array.new(n) {|i| Array.new(arr.first.size) {|j| st.include?([i,j]) ? 1 : 0}}
#=> [[1, 1, 1],
# [0, 1, 1],
# [0, 0, 1]]

How do I find the first two consecutive elements in my array of numbers?

Using Ruby 2.4, I have an array of unique, ordered numbers, for example
[1, 7, 8, 12, 14, 15]
How do I find the first two elements whose difference is 1? For example, the above array the answer to that is "7" and "8".
You could use each_cons and find to get the first element from the array of pairs where the second element less the first one is equal to 1:
p [1, 7, 8, 12, 14, 15].each_cons(2).find { |a, b| b - a == 1 }
# => [7, 8]
Here are three more ways.
#1
def first_adjacent_pair(arr)
(arr.size-2).times { |i| return arr[i, 2] if arr[i+1] == arr[i].next }
nil
end
first_adjacent_pair [1, 7, 8, 12, 14, 15] #=> [7,8]
first_adjacent_pair [1, 7, 5, 12, 14, 16] #=> nil
#2
def first_adjacent_pair(arr)
enum = arr.to_enum # or arr.each
loop do
curr = enum.next
nxt = enum.peek
return [curr, nxt] if nxt == curr.next
end
nil
end
enum.peek raises a StopIteration exception when the enumerator enum has generated its last element with the preceding enum.next. The exception is handled by Kernel#loop by breaking out of the loop, after which nil is returned. See also Object#to_enum, Enumerator#next and Enumerator#peek.
#3
def first_adjacent_pair(arr)
a = [nil, arr.first]
arr.each do |n|
a.rotate!
a[1] = n
return a if a[1] == a[0] + 1
end
nil
end
See Array#rotate!.
Simple example
X = [1, 7, 8, 12, 14, 15]
X.each_with_index do |item, index|
if index < X.count - 1
if (X[index+1]-X[index] == 1)
puts item
end
end
end
Here's an alternate method provided for educational purposes:
arr = [1, 7, 8, 12, 14, 15]
arr.each_cons(2).map {|v|v.reduce(:-)}.index(-1)
One way to do this:
a.each_with_index { |e, i| break [ e, a[i.next] ] if a[i.next] == e.next }
#=> [7, 8]
Unlike chunk or each_cons this doesn't create an array of arrays. It also breaks as soon as a pair is found.
Benchmarks
require 'fruity'
arr = ((1...1000)).to_a.reverse + [1,2]
def first_adjacent_pair(arr)
idx = arr.each_index.drop(1).find { |i| (arr[i-1]-arr[i]).abs == 1 }
idx ? arr[idx-1, 2] : nil
end
def first_adjacent_pair2(arr)
enum = arr.to_enum
loop do
curr = enum.next
nxt = enum.peek
return [curr, nxt] if (curr-nxt).abs == 1
end
nil
end
compare do
iceツ { ar = arr.dup; ar.each_with_index { |e, i| break [ e, ar[i.next] ] if ar[i.next] == e.next } }
cary { ar = arr.dup; first_adjacent_pair(ar) }
cary2 { ar = arr.dup; first_adjacent_pair2(ar) }
seb { ar = arr.dup; ar.each_cons(2).find{|a,b| b-a == 1} }
end
#Running each test 64 times. Test will take about 1 second.
#cary2 is faster than cary by 3x ± 0.1
#cary is faster than iceツ by 3x ± 0.1 (results differ: [999, 998] vs [1, 2])
#iceツ is faster than seb by 30.000000000000004% ± 10.0%

How do I select from an array if the next element is not one plus the current element?

I want to select all elements from my array such that the element immediately after it is not one plus the current element.
I tried this:
data_col = ["46", "47", "48", "49", "50", "51", "52"]
data_col.select.with_index{|string, index| data_col[index+1] && string.to_i != data_col[index+1].to_i + 1 }
# => ["46", "47", "48", "49", "50", "51"]
Every element is selected even though I would expect none of them (except the last one) gets selected. How can I modify my statement to get what I want?
(numbers + [nil]).each_cons(2).reject { |x, y| x.next == y }.map(&:first)
Add a nil at the end. We will use that to always include the last element:
[1, 2, 3] + [nil] # => [1, 2, 3, nil]
Pair each two consecutive elements:
[1, 2, 3].each_cons(2).to_a # => [[1, 2], [2, 3]]
Remove the pairs that don't meet your criteria:
[1, 2, 5, 6, nil].each_cons(2).reject { |x, y| x.next == y }
# => [[2, 5], [6, nil]]
Get the numbers out of the pairs:
[[2, 5], [6, nil]].map(&:first) # => [2, 6]
This is one way to use Enumerable#each_cons.
arr = [1, 3, 4, 6, 7, 9]
arr.each_cons(2).
each_with_object([]) { |(x,y),a| a << x unless x.next==y } << arr[-1]
#=> [1, 4, 7, 9]
Another option is to step through an enumerator using Enumerator#next and Enumerator#peek.
enum = arr.to_enum
a = []
loop do
x = enum.next
a << x unless x.succ == enum.peek
end
a << arr[-1]
#=> [1, 4, 7, 9]
When the enumerator is at its end, Enumerator#peek generates a StopIteration exception which is handled by Kernel#loop by breaking out of the loop.
Try
data_col.select.with_index { |number_string, index| data_col[index + 1].to_i != 1 + number_string.to_i }
Or, if don't mind that you have the result elements as integer,
n_col = data_col.map(&:to_i)
n_col.select.with_index{ |n, i| n_col[i + 1] != n + 1 }
data_col = ["46", "47", "48", "49", "50", "51", "52"]
def method(arg)
results = []
i = 0
while i < arg.length
current_num = arg[i].to_i
next_num = arg[i + 1].to_i
next_num - current_num != 1 ? results << current_num : nil
i += 1
end
return results
end
p method(data_col) #[52]
This will also return just [52] like you were looking for, it uses a simple while loop, and is pretty easy to understand what's going on.

Ruby: The most elegant way to detect turning points in Array

Let's take the following Array:
[1, 4, 5, 3, 1, 4, 6, 5, 4]
It has the following turning points (when rise changes to fall, or vice versa):
5 (at index 2)
1 (at index 4)
6 (at index 6)
To make task more general:
There is an Array a = [a1, a2, ...]
There is function p(x,y) -> z, where z is Comparable
How to get all elements ai ∈ a (0 < i < a.length-1) for which p(ai-1, ai) != p(ai, ai+1)
I would like to write something like:
a.detect_edges{|prev, n| prev >= n} # => [[5,2], [1, 4], [6,6]]
What's the most elegant way to get those turning points with their respective indexes? Here's my code with which I'm not satisfied from the aesthetic point of view:
class Array
def detect_edges(&blk)
return nil if self.length < 2
prev = blk.call(self[0], self[1])
result = []
self[0..-2].each_with_index do |elem, i|
current = blk.call(elem, self[i+1])
if current != prev
result.push [elem, i]
end
prev = current
end
result
end
end
[1, 4, 5, 3, 1, 4, 6, 5, 4]
.each_cons(3).with_index(1)
.reject{|(e1, e2, e3), i| (e1 <=> e2) == (e2 <=> e3)}
.map{|(e1, e2, e3), i| [e2, i]}
# => [[5, 2], [1, 4], [6, 6]]
Look ma, no map!
a = [1, 4, 5, 3, 1, 4, 6, 5, 4]
a[1..-2].each.with_index(1).reject { |e,i| (a[i-1]<=>e) == e<=>a[i+1] }
#=> [[5, 2], [1, 4], [6, 6]]
So you basically want the elements and their indices, where the element is the local max in a 1 index range:
arr.each.with_index.select { |element, index| element == arr[index.pred..index.next].max }
# => [[5, 2], [6, 6]]
Note, you have to handle the case for the first element or if elements are equal.
EDIT: for your updated version, you just have to check if the result of <=> has changed. Note that you will again have to check the case when elements are equal:
arr.each.with_index.to_a.tap(&:pop).drop(1).select do |element, index|
(arr[index.pred] <=> element) != (element <=> arr[index.next])
end # => [[5, 2], [1, 4], [6, 6]]
I don't see reason to get more fancy than:
class Array
def detect_edges
self.collect.with_index do |e, i|
next if i == 0 || i >= size-1
yield(self[i-1],e) != yield(e,self[i+1]) ? [e, i] : nil
end.compact
end
end
Note that when patching Array one should probably use refinements.

ruby array of hash merge array values based on key

I have some questions about an array of hash in ruby.
Example :
a = [{236=>1}, {236=>1}, {237=>1}]
I want get result like this :
a = [{236=>2}, {237=>1}]
How do I merge an array of values based on their key in ruby?
I find this way readable:
a = [{236=>1}, {236=>1}, {237=>1}]
merge_with_addition = -> x,y {x.merge(y){|_,old,new|old+new}}
p a.reduce(&merge_with_addition) #=> {236=>2, 237=>1}
There are several ways to do that. Here's one that uses the form of the method Hash#update (aka merge!) that uses a block to determine the values of keys present in both hashes being merged:
a.each_with_object({}) { |g,h|
h.update(g["order_detail_id"]=>g) { |_,oh,ng|
{ "order_detail_id" =>g["order_detail_id"],
"quantity"=>oh["quantity"]+ng["quantity"] } } }.values
#=> [{"order_detail_id"=>236, "quantity"=>2},
# {"order_detail_id"=>237, "quantity"=>1}]
Another approach uses Enumerable#group_by:
a.group_by { |h| h["order_detail_id"] }.map { |id, a|
{ "order_detail_id"=>id, "quantity"=>a.reduce(0) { |t,g|
t + g["quantity"] } } }
#=> [{"order_detail_id"=>236, "quantity"=>2},
# {"order_detail_id"=>237, "quantity"=>1}]
Edit: monoy has asked how one can merge:
a = [{236=>1}, {236=>1}, {237=>1}]
When you have hashes with a single key-value pair, it's often easiest to first convert them to arrays, to have easy access to the key and value:
b = a.map(&:to_a)
#=> [[[236, 1]], [[236, 1]], [[237, 1]]]
Unfortunately, this has three levels of arrays, where we want just two. We could write:
b = a.map(&:to_a).flatten(1)
#=> [[236, 1], [236, 1], [237, 1]]
but an easier way is:
b = a.flat_map(&:to_a)
#=> [[236, 1], [236, 1], [237, 1]]
Again, we can merge the arrays several different ways. This is one which uses a hash with a default value of zero:
b.each_with_object(Hash.new(0)) { |(k,v),h| h[k] += v }
#=> {236=>2, 237=>1}
We have:
enum = b.each_with_object(Hash.new(0))
#=> #<Enumerator: [[236, 1], [236, 1], [237, 1]]:each_with_object({})>
We can convert this enumerator to an array to see what values each will pass into the block:
enum.to_a
#=> [[[236, 1], {}], [[236, 1], {}], [[237, 1], {}]]
We can use Enumerator#next to obtain each value of the enumerator, and assign the block variables to that value. The first passed to the block is:
(k,v),h = enum.next
#=> [[236, 1], {}]
k #=> 236
v #=> 1
h #=> {}
so in the block we execute:
h[k] += v
which evaluates to:
h[236] += 1
which means:
h[236] = h[236] + 1
The hash h is presently empty, so it doesn't have a key 236. Therefore, h[236] on the right side of the expression returns the hash's default value of zero:
h[236] = 0 + 1
so now:
h #=> {236=>1}
Now pass the next element into the block:
(k,v),h = enum.next
#=> [[236, 1], {236=>1}]
k #=> 236
v #=> 1
h #=> {236=>1}
so now the default value is not used when we execute the expression in the block:
h[k] += v
#=> h[k] = h[k] + v
#=> h[236] = h[236] + 1
#=> h[236] = 1 + 1
so now:
h #=> {236=>2}
The third and last element of the enumerator is now passed to the block:
(k,v),h = enum.next
#=> [[237, 1], {236=>2}]
k #=> 237
v #=> 1
h #=> {236=>2}
h[k] += v
#=> h[k] = h[k] + v
#=> h[237] = h[237] + 1
#=> h[236] = 0 + 1 # the default value of 0 applies
h #=> {236=>2, 237=>1}
and we are finished.
array = [{234 => 1}, {235 => 1}, {234 => 1}]
hashes = array.map(&:to_a).flatten
.each_slice(2).with_object(Hash.new(0)) { |(k, v), h|
h[k] += v
}.each_with_object([]) { |(k, v), a|
a << { k => v }
}

Resources