Simplest way in Ruby to iterate over element plus next element, where "the next element" is nil on the last iteration?

Viewed 57

I want to loop over an array like [:foo, :bar, :baz].

On each iteration, I want the item and the next item. On the last iteration, the "next item" should be nil.

So I want to yield in turn :foo, :bar, then :bar, :baz, and finally :baz, nil.

I can think of a few ways of achieving this:

  • my_list.zip(my_list.from(1)) { |item, next_item| … }
  • my_list.each.with_index(1) { |item, i| next_item = my_list[i] }
  • [*my_list, nil].each_cons(2) { |item, next_item| … }

But I feel like I might be missing some simpler way. Am I?

1 Answers

If you only every have three non-nil elements in your Array and always want two elements at a time from #each_cons, then just append a nil and call it a day.

%i[foo bar baz].push(nil).each_cons(2).map { [_1, _2] }
#=> [[:foo, :bar], [:bar, :baz], [:baz, nil]]

Alternatively, you can just reference each indexed element of the named Array by the current or successor index number, because any reference to elements outside the Array's bounds will be nil. For example:

array = %i[foo bar baz]
array.each_index.map { [array[_1], array[_1.succ]] }
#=> [[:foo, :bar], [:bar, :baz], [:baz, nil]]

In either case, you'll need #map or some other mechanism if you want to return an Array of Array objects. If you want to do something else, like yield, you can do that too.

Note that if you're going to have a variable number of Array elements, or make changes to the number of #each_cons items, then you'll need a different approach. If that is the case, feel free to open a new or related question.

Related