Viola Crellin Viola Crellin - 4 years ago 118
Ruby Question

`Array#each_slice`, leaving remainders at the beginning

I'm trying to slice an array into groups of three. I want to have the remainders at the beginning (

[1, 2]
in the following example).

arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
...
#=> [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]


Is there a nifty way to do this?

The usual way to split an array would be:

arr.each_slice(3)
# => [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11]]


This gives the remainders
[10, 11]
at the end. I tried as below, thinking
each_slice
might accept a negative argument and read it as going backwards through the array.

arr.each_slice(-3)


Alas, it didn't work.

Answer Source

Though one might reverse an array thrice, there is more efficient way to achieve a goal:

arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
a = arr.dup # not to modify it inplace
[a.shift(a.size % 3)] + a.each_slice(3).to_a
#⇒ [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]

BTW, arr.each_slice(3) returns an enumerator, not an array as you posted in the question.

upd or, as suggested by Cary Swoveland, to void dup:

n = a.size % 3
[a[0...n]] + a[n..-1].each_slice(3).to_a

upd getting rid of dup by @sawa:

[a.first(a.size % 3)] + a.drop(a.size % 3).each_slice(3).to_a

upd just out of curiosity (assuming an input has no nil elements):

([nil] * (3 - a.size % 3) + a).each_slice(3).to_a.tap do |a|
  a.unshift(a.shift.compact!)
end

the above might be safely run on original array, it does not modify it inplace.

UPD2 as pointed out by Stefan in comments, any of the above will produce an initial empty slice if the array is divisible by 3. So, the proper solution (and, the fastest, btw) should look like:

(arr.size % 3).zero? ? arr.each_slice(3).to_a : ANY_OF_THE_ABOVE
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download