bluexuemei
bluexuemei

Reputation: 419

Grouping consecutive numbers in an array

I need to add consecutive numbers to a new array and, if it is not a consecutive number, add only that value to a new array:

old_array = [1, 2, 3, 5, 7, 8, 9, 20, 21, 23, 29]

I want to get this result:

 new_array = [
  [1,2,3],
  [5],
  [7,8,9]
  [20,21]
  [23],
  [29]
]

Is there an easier way to do this?

Upvotes: 9

Views: 3509

Answers (7)

avcJav
avcJav

Reputation: 335

A little late to this party but:

old_array.slice_when { |prev, curr| curr != prev.next }.to_a
# => [[1, 2, 3], [5], [7, 8, 9], [20, 21], [23], [29]]

Upvotes: 16

Shiyason
Shiyason

Reputation: 781

Using a Hash you can do:

counter = 0
groups = {}
old_array.each_with_index do |e, i|
  groups[counter] ||= []
  groups[counter].push old_array[i]
  counter += 1 unless old_array.include? e.next
end
new_array = groups.keys.map { |i| groups[i] }

Upvotes: 0

sawa
sawa

Reputation: 168091

This is the official answer given in RDoc (slightly modified):

actual = old_array.first
old_array.slice_before do
  |e|
  expected, actual = actual.next, e
  expected != actual
end.to_a

Upvotes: 9

Daniël Knippers
Daniël Knippers

Reputation: 3055

Some answers seem unnecessarily long, it is possible to do this in a very compact way:

arr = [1, 2, 3, 5, 7, 8, 9, 20, 21, 23, 29]
arr.inject([]) { |a,e| (a[-1] && e == a[-1][-1] + 1) ? a[-1] << e : a << [e]; a }
# [[1, 2, 3], [5], [7, 8, 9], [20, 21], [23], [29]]

Alternatively, starting with the first element to get rid of the a[-1] condition (needed for the case when a[-1] would be nil because a is empty):

arr[1..-1].inject([[arr[0]]]) { |a,e| e == a[-1][-1] + 1 ? a[-1] << e : a << [e]; a }
# [[1, 2, 3], [5], [7, 8, 9], [20, 21], [23], [29]]

Enumerable#inject iterates all elements of the enumerable, building up a result value which starts with the given object. I give it an empty Array or an Array with the first value wrapped in an Array respectively in my solutions. Then I simply check if the next element of the input Array we are iterating is equal to the last value of the last Array in the resulting Array plus 1 (i.e, if it is the next consecutive element). If it is, I append it to the last list. Otherwise, I start a new list with that element in it and append it to the resulting Array.

Upvotes: 1

Uri Agassi
Uri Agassi

Reputation: 37409

Using chunk you could do:

old_array.chunk([old_array[0],old_array[0]]) do |item, block_data|
  if item > block_data[1]+1
   block_data[0] = item
  end

  block_data[1] = item 
  block_data[0]
end.map { |_, i| i }
# => [[1, 2, 3], [5], [7, 8, 9], [20, 21], [23], [29]]

Upvotes: 1

John B
John B

Reputation: 3646

You could also do it like this:

old_array=[1, 2, 3, 5, 7, 8, 9, 20, 21, 23, 29]
new_array=[]
tmp=[]
prev=nil
for i in old_array.each
    if i != old_array[0]
        if i - prev == 1
            tmp << i
        else
            new_array << tmp
            tmp=[i]
        end
        if i == old_array[-1]
            new_array << tmp
            break
        end
        prev=i
    else
        prev=i
        tmp << i
    end
end

Upvotes: 0

Cary Swoveland
Cary Swoveland

Reputation: 110675

A couple other ways:

old_array = [1, 2, 3, 5, 7, 8, 9, 20, 21, 23, 29]

#1

a, b = [], []
enum = old_array.each
loop do
  b << enum.next
  unless enum.peek.eql?(b.last.succ)
    a << b
    b = []
  end
end
a << b if b.any?
a #=> [[1, 2, 3], [5], [7, 8, 9], [20, 21], [23], [29]]

#2

def pull_range(arr)
  b = arr.take_while.with_index { |e,i| e-i == arr.first }
  [b, arr[b.size..-1]]
end

b, l = [], a
while l.any?
  f, l = pull_range(l)
  b << f
end
b #=> [[1, 2, 3], [5], [7, 8, 9], [20, 21], [23], [29]]

Upvotes: 2

Related Questions