简体   繁体   中英

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.

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

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.

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

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM