Dividing elements of a ruby array into an exact number of (nearly) equal-sized sub-arrays

Ruby on-RailsRubyArraysRuby 1.8.7

Ruby on-Rails Problem Overview


I need a way to split an array in to an exact number of smaller arrays of roughly-equal size. Anyone have any method of doing this?

For instance

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] 
groups = a.method_i_need(3)
groups.inspect
    => [[1,2,3,4,5], [6,7,8,9], [10,11,12,13]]

> Note that this is an entirely separate problem from dividing an array into chunks, because a.each_slice(3).to_a would produce 5 groups (not 3, like we desire) and the final group may be a completely different size than the others: > > [[1,2,3], [4,5,6], [7,8,9], [10,11,12], [13]] # this is NOT desired here. >

In this problem, the desired number of chunks is specified in advance, and the sizes of each chunk will differ by 1 at most.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

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]]

Solution 2 - Ruby on-Rails

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

Solution 3 - Ruby on-Rails

Try

a.in_groups_of(3,false)

It will do your job

Solution 4 - Ruby on-Rails

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]]

Solution 5 - Ruby on-Rails

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]] 

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRedView Question on Stackoverflow
Solution 1 - Ruby on-RailsJoshua CheekView Answer on Stackoverflow
Solution 2 - Ruby on-RailsmltsyView Answer on Stackoverflow
Solution 3 - Ruby on-RailsParitosh SinghView Answer on Stackoverflow
Solution 4 - Ruby on-RailsMaxime BrehinView Answer on Stackoverflow
Solution 5 - Ruby on-Railsandroid.weaselView Answer on Stackoverflow