How to get sequence of array elements? - arrays

I have the following array that is "separated" by "blocks". Each invisible block begins with a letter having number 5 next to it and finishes when another letter with 5 next to it appears.
a = [
"F5","a4","g4","F5","a4","d4","F5","a4","g4","e3",
"H5","a4",
"Y5","a4","d4","Y5","g4","c3"
]
In this array there are 3 "blocks" like below.
Block1 --> begins with first "F5" (index 0) and ends in "e3" (index
9), just before the "H5".
Block2 --> begins with first "H5" (index 10) and ends in "a4" (index
11), just before the "Y5".
Block3 --> begins with first "Y5" (index 12) and ends in "c3" (index
17), when it reaches the end of array.
What I'd like to get, is a sequence (from 1 to N) of each element within each block (Not within the array itself), where the output would be:
b = [
["F5",1],["a4",1],["g4",1],["F5",2],["a4",2],["d4",1],["F5",3],["a4",3],["g4",2],["e3",1],
["H5",1],["a4",1],
["Y5",1],["a4",1],["d4",1],["Y5",2],["g4",1],["c3",1]
]
With my current attempt I'm getting only the count of each element and not the sequence. How can this be done? Thanks
a = [
"F5","a4","g4","F5","a4","d4","F5","a4","g4","e3",
"H5","a4",
"Y5","a4","d4","Y5","g4","c3"
]
b = []
a.each{|v|
b.push([v,a.count(v)])
}
=> [
["F5", 3], ["a4", 5], ["g4", 3], ["F5", 3], ["a4", 5], ["d4", 2], ["F5", 3], ["a4", 5], ["g4", 3], ["e3", 1],
["H5", 1], ["a4", 5],
["Y5", 2], ["a4", 5], ["d4", 2], ["Y5", 2], ["g4", 3], ["c3", 1]
]

We are given the array
a = ["F5","a4","g4","F5","a4","d4","F5","a4","g4","e3",
"H5","a4","Y5","a4","d4","Y5","g4","c3"]
The problem can be viewed as having two steps, the first being to convert a to
arr = [["F5","a4","g4","F5","a4","d4","F5","a4","g4","e3"],
["H5","a4"], ["Y5","a4","d4","Y5","g4","c3"]]
The second step is to construct the desired array from arr.
Step 1
I understand the rule for converting a to arr is as follows.
the first element of each array of arr is a string of the form YZ that matches the regular expression /[A-Z]\d/ (but the digit does not necessarily equal 5).
it is assumed that the first element of a always has the above property.
the last element of each array b of arr is the last element of a or the element of a that precedes the first element of a following the element of a that corresponds to the first element of b that matches /[A-Z]\d/, but when compared with the first element of b the capital letters differ and the digits are equal.
We may write
frst = a.first
arr = a.slice_before do |s|
(s[0].match?(/[A-Z]/) && s[0] != frst[0] && s[1] == frst[1]) ?
(frst = s) : false
end.to_a
#=> [["F5", "a4", "g4", "F5", "a4", "d4", "F5", "a4", "g4", "e3"]],
# ["H5", "a4"], ["Y5", "a4", "d4", "Y5", "g4", "c3"]]
See Enumerable#slice_before.
Step 2
We may now convert arr to the desired array as follows.
arr.map do |e|
h = Hash.new(0)
e.map { |s| [s, h[s] += 1] }
end
#=> [[["F5", 1], ["a4", 1], ["g4", 1], ["F5", 2], ["a4", 2], ["d4", 1],
# ["F5", 3], ["a4", 3], ["g4", 2], ["e3", 1]],
# [["H5", 1], ["a4", 1]],
# [["Y5", 1], ["a4", 1], ["d4", 1], ["Y5", 2], ["g4", 1], ["c3", 1]]]
Here Hash::new is used to create an empty hash h with a default value of zero. All that means is that if h does not have a key k, h[k] returns zero. As h[k] += 1 can be seen as
h[k] = h[k] + 1
h[k] on the right returns 1 when h does not have a key k. Another way of writing that is
h[k] = h.fetch(k,0) + 1
See Hash#fetch.

This is what I'd do:
Starting with:
a_ary = [
'F5', 'a4', 'g4', 'F5', 'a4', 'd4', 'F5', 'a4', 'g4', 'e3',
'H5', 'a4',
'Y5', 'a4', 'd4', 'Y5', 'g4', 'c3'
]
BREAK_REGEX = /^.5/
I'd group the array by matching blocks, then grab the resulting chunks and flatten them back to the collected arrays.
grouped_ary = a_ary.slice_before(BREAK_REGEX)
.group_by { |a| a.first } # => {"F5"=>[["F5", "a4", "g4"], ["F5", "a4", "d4"], ["F5", "a4", "g4", "e3"]], "H5"=>[["H5", "a4"]], "Y5"=>[["Y5", "a4", "d4"], ["Y5", "g4", "c3"]]}
.values # => [[["F5", "a4", "g4"], ["F5", "a4", "d4"], ["F5", "a4", "g4", "e3"]], [["H5", "a4"]], [["Y5", "a4", "d4"], ["Y5", "g4", "c3"]]]
.map(&:flatten) # => [["F5", "a4", "g4", "F5", "a4", "d4", "F5", "a4", "g4", "e3"], ["H5", "a4"], ["Y5", "a4", "d4", "Y5", "g4", "c3"]]
Then process those, counting the occurrences in each array, looping over them and output the element and the count, resetting the counter for each block:
b_ary = grouped_ary.flat_map{ |e|
element_count = Hash.new { |h, k| h[k] = 0 }
e.map { |i|
element_count[i] += 1
[
i,
element_count[i]
]
}
}
Which results in:
b_ary
# => [["F5", 1],
# ["a4", 1],
# ["g4", 1],
# ["F5", 2],
# ["a4", 2],
# ["d4", 1],
# ["F5", 3],
# ["a4", 3],
# ["g4", 2],
# ["e3", 1],
# ["H5", 1],
# ["a4", 1],
# ["Y5", 1],
# ["a4", 1],
# ["d4", 1],
# ["Y5", 2],
# ["g4", 1],
# ["c3", 1]]
# ]
Here's what the very first step using slice_before creates:
a_ary.slice_before(BREAK_REGEX).to_a
# => [["F5", "a4", "g4"],
# ["F5", "a4", "d4"],
# ["F5", "a4", "g4", "e3"],
# ["H5", "a4"],
# ["Y5", "a4", "d4"],
# ["Y5", "g4", "c3"]]
Enumerable is where the magic lies.

I'd break this into a few functions.
require 'minitest/autorun'
# Split array where the given block returns true
def group_by_sequence(x)
i = 0
x.each_with_object([]) { |e, a|
i += 1 if yield e
a[i] = [] if a[i].nil?
a[i] << e
}
end
# Split array at each new "five", eg. going from "A5" to "B5"
def group_by_fives(x)
li = nil
group_by_sequence(x) { |e|
l, n = e.chars
li = l if li.nil?
if n == '5' && li != l
li = l
true
else
false
end
}
end
# Given [A,B,C,B], returns [[A,1], [B,1], [C,1], [B,2]]
def add_counts_by_group(x)
group_by_fives(x).flat_map { |group|
count = {}
group.map { |e|
count[e] = count[e].to_i + 1
[e, count[e]]
}
}
end
class Test < Minitest::Test
def test_add_counts_by_group
assert_equal(
[
["F5",1],["a4",1],["g4",1],["F5",2],["a4",2],["d4",1],["F5",3],["a4",3],["g4",2],["e3",1],
["H5",1],["a4",1],
["Y5",1],["a4",1],["d4",1],["Y5",2],["g4",1],["c3",1]
],
add_counts_by_group([
"F5","a4","g4","F5","a4","d4","F5","a4","g4","e3",
"H5","a4",
"Y5","a4","d4","Y5","g4","c3"
])
)
end
end

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.

Sort an array of arrays based on the order in another array

I have an array of arrays:
x = [
["ready", 5], ["shipped", 1], ["pending", 1], ["refunded", 1],
["delivered", 23], ["scheduled", 1], ["canceled", 51]
]
My sorting array is
order_array = [
"ready", "in_progress", "recieved", "shipped", "scheduled", "pick_up",
"delivered", "canceled", "failed", "refunded", "refund_failed"
]
I need to order x based on the value of the first element in each subarray. The required sorted array is:
[
["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23],
["canceled", 51], ["refunded", 1]
]
Using sort_by doesn't result in the required sorting, it leads to the same array.
result = x.sort_by {|u| order_array.index(u)}
# => [
# ["ready", 5], ["shipped", 1], ["pending", 1], ["refunded", 1],
# ["delivered", 23], ["scheduled", 1], ["canceled", 51]
# ]
h = x.to_h
# => {"ready"=>5,
# "shipped"=>1,
# "pending"=>1,
# "refunded"=>1,
# "delivered"=>23,
# "scheduled"=>1,
# "canceled"=>51}
order_array.map{|key| [key, h[key]] if h.key?(key)}.compact
# => [["ready", 5],
# ["shipped", 1],
# ["scheduled", 1],
# ["delivered", 23],
# ["canceled", 51],
# ["refunded", 1]]
or
h = x.to_h{|k, v| [k, [k, v]]}
#=> {"ready"=>["ready", 5],
# "shipped"=>["shipped", 1],
# "pending"=>["pending", 1],
# "refunded"=>["refunded", 1],
# "delivered"=>["delivered", 23],
# "scheduled"=>["scheduled", 1],
# "canceled"=>["canceled", 51]}
order_array.map{|k| h[k]}.compact
#=> [["ready", 5],
# ["shipped", 1],
# ["scheduled", 1],
# ["delivered", 23],
# ["canceled", 51],
# ["refunded", 1]]
or
h = x.to_h{|k, v| [k, [k, v]]}
#=> {"ready"=>["ready", 5],
# "shipped"=>["shipped", 1],
# "pending"=>["pending", 1],
# "refunded"=>["refunded", 1],
# "delivered"=>["delivered", 23],
# "scheduled"=>["scheduled", 1],
# "canceled"=>["canceled", 51]}
h.values_at(*order_array).compact
#=> [["ready", 5],
# ["shipped", 1],
# ["scheduled", 1],
# ["delivered", 23],
# ["canceled", 51],
# ["refunded", 1]]
assoc seems helpful: "Searches through an array whose elements are also arrays comparing obj with the first element of each contained array using obj.==."
order_array.map{|e| x.assoc(e) }.compact
You're almost there with this: index isn't working as you're comparing the full array, rather than the first element of it. This will work:
result = x.sort_by { |u| order_array.index(u[0]) || 100 }
#=> [["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23], ["canceled", 51], ["refunded", 1], ["pending", 1]]
Please note, the 100 is there to default to the back of the sort if the value isn't found in order_array.
Edit
This was initially accepted, despite including ["pending", 1] suggesting it fit the requirements; however, here's a solution to avoid the unwanted entry, which also handles duplicates should the need arise.
order_array.each_with_object([]) { |ordered_by, array| array.push(*x.select { |item| item[0] == ordered_by }) }
#=> [["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23], ["canceled", 51], ["refunded", 1]]
Or, very fast though still allowing for duplicate values under each ordered item:
hash = x.each_with_object(Hash.new { |h,k| h[k] = [] }) { |item, h| h[item[0]] << item[1] }
order_array.flat_map { |key| [key, hash[key]] }
Benchmark
Here's a benchmark for this scenario with a larger dataset: https://repl.it/repls/SentimentalAdequateClick. Looks like Sawa's methods lead the way, though my last effort works handily should there be duplicate values in future. Also, my second effort sucks (which surprised me a little) :)
I'd suggest
x.keep_if { |e| order_array.include? e[0] }.sort_by { |e| order_array.index(e[0]) }
Since some values are not elements of order_array, for example "pending".
#=> [["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23], ["canceled", 51], ["refunded", 1]]
Benchmarked the answers up to now 500.times:
# user system total real
# sawa 0.006698 0.000132 0.006830 ( 0.006996) # on the first method
# ray 0.005543 0.000123 0.005666 ( 0.005770)
# igian 0.001923 0.000003 0.001926 ( 0.001927)
# srack 0.005270 0.000168 0.005438 ( 0.005540) # on the last method
Just for fun I tried to find a faster method for Ruby 2.5:
xx = x.to_h # less than Ruby 2.6
order_array.each.with_object([]) { |k, res| res << [k, xx[k]] if xx.has_key? k }
You can try below code to find output efficiently,
order_array.map { |p| x.detect { |y| y[0] == p } }.compact
# => [["ready", 5], ["shipped", 1], ["scheduled", 1], ["delivered", 23], ["canceled", 51], ["refunded", 1]]
I've assumed:
the first element of each element of x is not necessarily unique;
all elements of x whose first element is the same and whose first element is a member of order_array appear consecutively in the returned (sorted) array in the order in which those elements appear in x;
any elements of x whose first element is not a member of order_array appears in the returned (sorted) array after all elements whose first element is in sorted_array, and all such elements appear in the returned array (at the end) in the order in which they occur in x; and
efficiency is paramount.
x = [
["ready", 5], ["shipped", 1], ["pending", 1], ["refunded", 1], ["originated", 3],
["delivered", 23], ["scheduled", 1], ["ready", 8], ["canceled", 51]
]
order_array = [
"ready", "in_progress", "received", "shipped", "scheduled", "pick_up",
"delivered", "canceled", "failed", "refunded", "refund_failed"
]
order_pos = order_array.each_with_object({}) { |word,h| h[word] = [] }
#=> {"ready"=>[], "in_progress"=>[], "received"=>[], "shipped"=>[],
# "scheduled"=>[], "pick_up"=>[], "delivered"=>[], "canceled"=>[],
# "failed"=>[], "refunded"=>[], "refund_failed"=>[]}
back = x.each_with_index.with_object([]) { |((word,v),i),back|
order_pos.key?(word) ? (order_pos[word] << i) : back << [word,v] }
#=> [["pending", 1], ["originated", 3]]
order_pos.flat_map { |word,offsets| offsets.map { |i| x[i] } }.concat(back)
#=> [["ready", 5], ["ready", 8], ["shipped", 1], ["scheduled", 1],
# ["delivered", 23], ["canceled", 51], ["refunded", 1], ["pending", 1],
# ["originated", 3]]
Note:
order_pos
#=> {"ready"=>[0, 7], "in_progress"=>[], "received"=>[], "shipped"=>[1],
# "scheduled"=>[6], "pick_up"==>[], "delivered"=>[5], "canceled"=>[8],
# "failed"=>[], "refunded"=>[3], "refund_failed"=>[]}
It is necessary to initialise order_pos in order for its keys to be ordered by order_arr. This is an example of the worth of a controversial change made in Ruby 1.9 which guaranteed that hash keys will remain in key-insertion order.

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

Finding index and size of consecutive repeated elements in an array

An array consists of 1, 2, and 0s. I am trying to identify the maximum repetition and its starting index within the array.
Example:
2 2 1 0 2 2 2 0 1 1
The method should accept an integer arguement, which can be one of the numbers 1 or 2
If we demonstrate these inputs on above array, the outputs would be:
find_duplicates(2)
=> 3,4
find_duplicates(1)
=> 2,8
where the first number indicates the size of the duplication, and second is the starting index of it.
I tried looping through the array and compare with arr[i+1] or arr[-1], but this is not the correct approach. Any help will be greatly appreciated.
Edit:
I had not pasted what I had tried at the time I asked the question, this is not something I would do if I could feel some confidence on the way I followed:
def find_status(arr,participant)
status = Array.new
#arr is a two dimensional array
for i in 0...arr.length do
current_line=arr[i]
cons=0
for j in 0...current_line.length do
#I worked on lots of if/else/case statements here, this is just one of them
if current_line[j] == participant
cons+=1 #count consecutive
if current_line[j]!=participant
cons=0
end
end
status[i] = cons
end
end
return status
end
def max_run(arr, target)
_,b = arr.each_with_index.
chunk { |n,_| n==target }.
select { |tf,_| tf==true }.
max_by { |_,a| a.size }
b ? [b.size, b.first.last] : nil
end
arr = [1,1,2,2,2,3,1,1,1,1,2,2,2,2,3,3]
max_run(arr,1) #=> [4, 6]
max_run(arr,2) #=> [4, 10]
max_run(arr,3) #=> [2, 14]
max_run(arr,4) #=> nil
For target = 2, the steps are as follows:
enum0 = arr.each_with_index
#=> #<Enumerator: [1, 1, 2, 2, 2, 3, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3]
# :each_with_index>
We can see the elements that will be generated by this enumerator by converting it to an array:
enum0.to_a
#=> [[1, 0], [1, 1], [2, 2], [2, 3], [2, 4], [3, 5], [1, 6], [1, 7], [1, 8],
# [1, 9], [2, 10], [2, 11], [2, 12], [2, 13], [3, 14], [3, 15]]
Continuing,
enum1 = enum0.chunk { |n,_| n==target }
#=> #<Enumerator: #<Enumerator::Generator:0x007f9beb9b0850>:each>
Carefully examine the return value here. You can think of enum1 as a "compound enumerator". It will generate the following values:
enum1.to_a
#=> [[false, [[1, 0], [1, 1]]], [true, [[2, 2], [2, 3], [2, 4]]],
# [false, [[3, 5], [1, 6], [1, 7], [1, 8], [1, 9]]],
# [true, [[2, 10], [2, 11], [2, 12], [2, 13]]], [false, [[3, 14], [3, 15]]]]
Continuing,
c = enum1.select { |tf,_| tf==true }
#=> [[true, [[2, 2], [2, 3], [2, 4]]],
# [true, [[2, 10], [2, 11], [2, 12], [2, 13]]]]
_,b = c.max_by { |_,a| a.size }
#=> [true, [[2, 10], [2, 11], [2, 12], [2, 13]]]
b #=> [[2, 10], [2, 11], [2, 12], [2, 13]]
b ? [b.size, b.first.last] : nil
#=> [[2, 10], [2, 11], [2, 12], [2, 13]] ? [4, [2,10].last]
#=> [4, 10]
a = [2, 2, 1, 0, 2, 2, 2, 0, 1, 1]
longest_sequence =
a.each_index.select{|i| a[i] == 2}.chunk_while{|i, j| i.next == j}.max_by(&:length)
# => [4, 5, 6]
[longest_sequence.length, longest_sequence.first] # => [3, 4]
The solution below is likely most efficient since it is O(N). It walks through an array, collecting the chunks:
arr.each.with_index.reduce({idx:-1, i: -1, len: 0}) do |memo, (e, i)|
memo[:i] = i if memo[:i] == -1 && e == 2 # at the beginning of chunk
memo[:len], memo[:idx] = [i - memo[:i], memo[:i]] \
if memo[:i] >= 0 && i - memo[:i] > memo[:len] # save values if needed
memo[:i] = -1 unless e == 2 # reset index counter
memo
end.reject { |k, _| k == :i } # reject temporary index value
#⇒ {
# :idx => 4,
# :len => 3
# }
To use it as method, accepting a parameter; just wrap the code above with def find_duplicates number and substitute 2 with number in the code above. Yes, it returns hash instead of an array.

Ruby grouping elements

I have array:
a = [1, 3, 1, 3, 2, 1, 2]
And I want to group by values, but save it indexes, so result must be looks like this:
[[0, 2, 5], [1, 3], [4, 6]]
or hash
{1=>[0, 2, 5], 3=>[1, 3], 2=>[4, 6]}
Now I'm using pretty ugly and big code:
struc = Struct.new(:index, :value)
array = array.map.with_index{ |v, i| struc.new(i, v) }.group_by {|s| s[1]}.map { |h| h[1].map { |e| e[0]}}
`
If you use a hash with a default value to avoid iterating twice over the elements:
a = [1, 3, 1, 3, 2, 1, 2]
Hash.new { |h, k| h[k] = [] }.tap do |result|
a.each_with_index { |i, n| result[i] << n }
end
#=> { 1 => [0, 2, 5], 3 => [1, 3], 2 => [4, 6] }
a = [1, 3, 1, 3, 2, 1, 2]
a.each_with_index.group_by(&:first).values.map { |h| h.map &:last }
First we get an Enumerator in the form [val, idx], ... (each_with_index), then group_by the value (first value in pair), then take the index (last element) of each pair.
You can use:
Enumerable#each_with_index
Enumerable#group_by and
Array#transpose:
a = [1, 3, 1, 3, 2, 1, 2]
a.each_with_index.group_by(&:first).values.map { |b| b.transpose.last }
#=> [[0, 2, 5], [1, 3], [4, 6]]

Resources