9

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?

3
  • 3
    Is this for school? What have you tried? Are you asking for help or for us to do it for you? Commented May 24, 2014 at 2:46
  • Condition for new_array? Commented May 24, 2014 at 3:04
  • Related question: stackoverflow.com/q/3728660/38765 Commented May 29, 2014 at 23:39

7 Answers 7

16

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]]
Sign up to request clarification or add additional context in comments.

1 Comment

+1. slice_when was added to ruby version 2.2, and provides a more elegant solution than these older answers.
9

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

2 Comments

Ruby 1.8.7 is no longer maintained and contains several known security vulnerabilities. You should avoid using it.
@AndrewMarshall Ruby 1.8.7 is being maintained for security vulnerabilities until next month at least. Not a good idea to use 1.8.7, though. ruby-lang.org/en/news/2013/12/17/maintenance-of-1-8-7-and-1-9-2
2

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

2 Comments

Probably not supposed to say this, but that sir is a work of art!
Thanks for the compliment, @John. (John was referring to #1; I added #2 later.)
1

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

Comments

1

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.

Comments

0

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.