devwanderer
devwanderer

Reputation: 75

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

Upvotes: 0

Views: 310

Answers (3)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

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.

Upvotes: 1

Cary Swoveland
Cary Swoveland

Reputation: 110675

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] 

Upvotes: 3

sawa
sawa

Reputation: 168101

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]

Upvotes: 2

Related Questions