ecki
ecki

Reputation: 790

How to count consecutive numbers in an array?

If I have an array:

array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]

I want to be able to identify consecutive matching numbers that have a length of greater than 3. And map the starting index of the consecutive numbers. An example output for the above array would be:

consecutive_numbers = [
  {starting_index: 1, value: 2, length: 4},
  {starting_index: 10, value: 3, length: 4},
  {starting_index: 14, value: 2, length: 7}
]

The values can be the same, but the consecutive series' must be mutually exclusive. See that there are 2 hashes with a value of 2, but their starting indexes are different.

My attempt so far... looks like this:

array.each_cons(3).with_index.select{|(a,b,c), i| 
  [a,b,c].uniq.length == 1
}

but that will returns:

[[[2, 2, 2], 1], [[2, 2, 2], 2], [[1, 1, 1], 7], [[3, 3, 3], 10], [[3, 3, 3], 11], [[2, 2, 2], 14], [[2, 2, 2], 15], [[2, 2, 2], 16], [[2, 2, 2], 17], [[2, 2, 2], 18]]

But that returns overlapping results.

Upvotes: 3

Views: 1708

Answers (5)

iGian
iGian

Reputation: 11183

This is another option..


array
     .zip(0..)
     .slice_when { |a, b| a.first != b.first }
     .map { |a| { starting_index: a.first.last, value: a.first.first, length: a.size } }
     .reject { |h| h[:length] < 3 }

#=> [{:starting_index=>1, :value=>2, :length=>4}, {:starting_index=>7, :value=>1, :length=>3}, {:starting_index=>10, :value=>3, :length=>4}, {:starting_index=>14, :value=>2, :length=>7}]

Upvotes: 2

Cary Swoveland
Cary Swoveland

Reputation: 110675

array.each_with_index.
      chunk(&:first).
      select { |_,a| a.size > 3 }.
      map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
  #=> [{:starting_index=> 1, :value=>2, :length=>4},
  #    {:starting_index=>10, :value=>3, :length=>4},
  #    {:starting_index=>14, :value=>2, :length=>7}] 

The steps are as follows.

e = array.each_with_index.chunk(&:first)
  #=> #<Enumerator: #<Enumerator::Generator:0x00005b1944253c18>:each> 

We can convert this enumerator to an array to view the elements it will generate and pass to its block.

e.to_a
  #=> [[1, [[1, 0]]],
  #    [2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
  #    [5, [[5, 5], [5, 6]]],
  #    [1, [[1, 7], [1, 8], [1, 9]]],
  #    [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
  #    [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]] 

Continuing,

c = e.select { |_,a| a.size > 3 }
  #=> [[2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
  #    [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
  #    [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]] 
c.map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
  #=> [{:starting_index=> 1, :value=>2, :length=>4},
  #    {:starting_index=>10, :value=>3, :length=>4},
  #    {:starting_index=>14, :value=>2, :length=>7}] 

This is another way.

array.each_with_index.with_object([]) do |(n,i),arr|
  if arr.any? && arr.last[:value] == n
    arr.last[:length] += 1
  else
    arr << { starting_index: i, value: n, length: 1 }
  end
end.select { |h| h[:length] > 3 }
  #=> [{:starting_index=> 1, :value=>2, :length=>4},
  #    {:starting_index=>10, :value=>3, :length=>4},
  #    {:starting_index=>14, :value=>2, :length=>7}]     

Upvotes: 4

Sebasti&#225;n Palma
Sebasti&#225;n Palma

Reputation: 33420

You can chunk_while each pair of elements are equal:

p array.chunk_while { |a, b| a == b }.to_a
# [[1], [2, 2, 2, 2], [5, 5], [1, 1, 1], [3, 3, 3, 3], [2, 2, 2, 2, 2, 2, 2]]

You select the arrays with 3 or more elements.

After that, with then, you can yield self, so you have access to the array of arrays, which you can use to get the starting_index:

[1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].chunk_while(&:==).then do |this|
  this.each_with_object([]).with_index do |(e, memo), index|
    memo << { starting_index: this.to_a[0...index].flatten.size, value: e.first, length: e.size }
  end
end.select { |e| e[:length] > 3 }

# [{:starting_index=>1, :value=>2, :length=>4},
#  {:starting_index=>10, :value=>3, :length=>4},
#  {:starting_index=>14, :value=>2, :length=>7}]

For the starting_index, you get the elements to the current index (non inclusive), flatten them, and get the total of elements.

The value, as each array in the array has the same elements, can be anything, the length, is the length of the current array in the "main" array.

Upvotes: 2

user3574603
user3574603

Reputation: 3618

You could work with strings instead.

Here, I coerce the array into a string:

input_sequence = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].join

I use a regex to group consecutive characters:

groups = input_sequence.gsub(/(.)\1*/).to_a
#=> ["1", "2222", "55", "111", "3333", "2222222"]

Now I can search for the groups as substrings within the input string:

groups.map do |group|
  {
    starting_index: input_sequence.index(group), 
    value: group[0].to_i,
    length: group.length
  }
end.reject { |group| group[:length] <= 3 }

#=> [{:starting_index=>1, :value=>2, :length=>4},
     {:starting_index=>7, :value=>1, :length=>3},
     {:starting_index=>10, :value=>3, :length=>4},
     {:starting_index=>14, :value=>2, :length=>7}]

There's room for improvement here -- I'm creating lots of intermediate objects for one -- but I thought I would offer a different approach.

Upvotes: 0

neversleep
neversleep

Reputation: 321

Well, the most obvious (and probably the fastest) way is iterate over an array and count everything manually:

array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]
array_length_pred = array.length.pred

consecutive_numbers = []

starting_index = 0
value = array.first
length = 1

array.each_with_index do |v, i|
  if v != value || i == array_length_pred
    length += 1 if i == array_length_pred && value == v

    if length >= 3
      consecutive_numbers << {
        starting_index: starting_index,
        value: value,
        length: length
      }
    end

    starting_index = i
    value = v
    length = 1
    next
  end

  length += 1
end

p consecutive_numbers

# [{:starting_index=>1, :value=>2, :length=>4},
# {:starting_index=>7, :value=>1, :length=>3},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]

Upvotes: 0

Related Questions