Viola Crellin
Viola Crellin

Reputation: 61

`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.

Upvotes: 2

Views: 341

Answers (2)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

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

Upvotes: 10

shivam
shivam

Reputation: 16506

A simple combination of reverse and each_slice will do the trick:

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

Explanation:

I am reversing the Array and doing each_slice. That will give me:

[[11, 10, 9], [8, 7, 6], [5, 4, 3], [2, 1]]

Now I'm iterating over this and reversing each sub-arrays to match the expected sub-array sequence with .map(&:reverse). At last I am reversing the whole Array to get the desired sequence (with last .reverse).

Upvotes: 7

Related Questions