Reputation: 61
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
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
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