64

I need a way to split an array in to a bunch of arrays within another array of equal size. Anyone have any method of doing this?

For instance

a = [0, 1, 2, 3, 4, 5, 6, 7]
a.method_i_need(3)
a.inspect
    => [[0,1,2], [3,4,5], [6,7]]
Red
  • 2,096
  • 5
  • 21
  • 36
  • 8
    It's unfortunate that the chosen example has the same result for "split in 3 groups" and "split in groups of 3 elements", that's why you got two completely different answers. – tokland Mar 01 '13 at 09:22
  • 1
    This question is different to the linked question. The linked question is to split an array into equal, known, sizes; this question is to split an array into an equal number of chunks, each of similar size. – Barry Kelly Dec 18 '19 at 19:17
  • 1
    Seconded: this is not a duplicate of the linked question, and this is the top result for a seach for "ruby slice array into n equal parts". Here is my Ruby-only (Rails not required) answer to the same problem on another question: https://stackoverflow.com/a/63040779 – nitrogen Jul 22 '20 at 18:27

5 Answers5

124

You're looking for Enumerable#each_slice

a = [0, 1, 2, 3, 4, 5, 6, 7]
a.each_slice(3) # => #<Enumerator: [0, 1, 2, 3, 4, 5, 6, 7]:each_slice(3)>
a.each_slice(3).to_a # => [[0, 1, 2], [3, 4, 5], [6, 7]]
Joshua Cheek
  • 26,664
  • 15
  • 67
  • 81
  • 30
    Just a note. This splits the array into groups of size 3. not into 3 equal sized groups. – yasith Sep 16 '15 at 22:12
  • If the array size does not divide evenly into the number of slices, is it possible to merge the remainder slice with the previous slice? Given your example, `[6, 7]` would be merged with `[3, 4, 5]` to make `[3, 4, 5, 6, 7]`. – Mohamad Apr 11 '16 at 16:38
  • 1
    @BorisStitnicky That probably speaks to the bad UX of documentation sites. Sometimes, it's easier to search by what you want to do, rather than having to know which class has the method you want. Googling is the thing, and if the results are SO, then that is it. There are noobs as well as programmers unfamiliar with a given language that use this. Skill is a pyramid, and there will be more noobs than experts. – ahnbizcad Jul 24 '16 at 18:06
114

Perhaps I'm misreading the question since the other answer is already accepted, but it sounded like you wanted to split the array in to 3 equal groups, regardless of the size of each group, rather than split it into N groups of 3 as the previous answers do. If that's what you're looking for, Rails (ActiveSupport) also has a method called in_groups:

a = [0,1,2,3,4,5,6]
a.in_groups(2) # => [[0,1,2,3],[4,5,6,nil]]
a.in_groups(3, false) # => [[0,1,2],[3,4], [5,6]]

I don't think there is a ruby equivalent, however, you can get roughly the same results by adding this simple method:

class Array; def in_groups(num_groups)
  return [] if num_groups == 0
  slice_size = (self.size/Float(num_groups)).ceil
  groups = self.each_slice(slice_size).to_a
end; end

a.in_groups(3) # => [[0,1,2], [3,4,5], [6]]

The only difference (as you can see) is that this won't spread the "empty space" across all the groups; every group but the last is equal in size, and the last group always holds the remainder plus all the "empty space".

Update: As @rimsky astutely pointed out, the above method will not always result in the correct number of groups (sometimes it will create multiple "empty groups" at the end, and leave them out). Here's an updated version, pared down from ActiveSupport's definition which spreads the extras out to fill the requested number of groups.

def in_groups(number)
  group_size = size / number
  leftovers = size % number

  groups = []
  start = 0
  number.times do |index|
    length = group_size + (leftovers > 0 && leftovers > index ? 1 : 0)
    groups << slice(start, length)
    start += length
  end

  groups
end
mltsy
  • 5,344
  • 1
  • 30
  • 41
  • 4
    I know this is an old post, but for those who are considering the ruby equivalent above, it's not quite correct. If you try to split an array of 20 elements into 11 groups, you'll end up with only 10 groups. slice_size will be 2, and 20 is divisible by 2. – rimsky Jan 23 '14 at 21:47
  • This is what I came here looking for. Not for groups of n size, like the accepted answer. Thank you. – Jeff Zivkovic Sep 11 '16 at 23:24
  • Nice catch @rimsky! Updated ;) – mltsy Feb 15 '17 at 17:44
  • It appears @rimsky's comment is not longer correct, at least of ActiveSupport 4.1.16. running on an array with 20 elements works: `[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9].in_groups(11).count` => 11 – agbodike Mar 18 '17 at 21:42
  • The ActiveSupport version has always worked. @rimsky was pointing out that my original (simplified) method above had that flaw. – mltsy Apr 14 '17 at 19:55
16

Try

a.in_groups_of(3,false)

It will do your job

Phrogz
  • 271,922
  • 98
  • 616
  • 693
Paritosh Singh
  • 5,674
  • 4
  • 33
  • 54
  • 19
    Note that [`in_groups_of`](http://rails.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Array/Grouping.html) is specific to Rails (or rather, ActiveSupport), while @Joshua's answer is usable in Ruby everywhere. Still, +1 for providing a working solution. – Phrogz Sep 11 '12 at 17:14
  • It's also only on [Array](https://github.com/rails/rails/blob/ccf9577aee86ce1f766c5e8854e0c285dc38f8ac/activesupport/lib/active_support/core_ext/array/grouping.rb). – Joshua Cheek Sep 11 '12 at 17:16
  • Note that the second parameter is the filler value (by default `nil`) in case array size is not dividable by the first parameter. – thisismydesign Dec 04 '18 at 12:08
3

As mltsy wrote, in_groups(n, false) should do the job.

I just wanted to add a small trick to get the right balance my_array.in_group(my_array.size.quo(max_size).ceil, false).

Here is an example to illustrate that trick:

a = (0..8).to_a
a.in_groups(4, false) => [[0, 1, 2], [3, 4], [5, 6], [7, 8]]
a.in_groups(a.size.quo(4).ceil, false) => [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
Maxime Brehin
  • 297
  • 3
  • 4
2

This needs some better cleverness to smear out the extra pieces, but it's a reasonable start.

def i_need(bits, r)
  c = r.count
  (1..bits - 1).map { |i| r.shift((c + i) * 1.0 / bits ) } + [r]
end

>   i_need(2, [1, 3, 5, 7, 2, 4, 6, 8])
 => [[1, 3, 5, 7], [2, 4, 6, 8]] 
> i_need(3, [1, 3, 5, 7, 2, 4, 6, 8])
 => [[1, 3, 5], [7, 2, 4], [6, 8]] 
> i_need(5, [1, 3, 5, 7, 2, 4, 6, 8])
 => [[1, 3], [5, 7], [2, 4], [6], [8]] 
android.weasel
  • 2,985
  • 1
  • 23
  • 37