2

I have a large array that I would like to split evenly into n arrays.

  • I have an array of 100 elements. I would like to split it evenly into 4 arrays. This would give me 4 arrays of 25 elements each.
  • I have an array of 100 elements. I would like to split it evenly into 3 arrays. Since I cannot evenly split it into the sub-arrays, then I want something like 2 arrays of 33 elements and one array of 34 elements.
  • I have an array of 2 elements. I would like to split it evenly into 4 arrays. Since I cannot split it evenly and some arrays will be empty, then I want something like 2 arrays of 1 element and 2 empty arrays.

I tried using each_slice, but that only slices the array into small parts based on the number argument passed to it.

How can I do this?

the Tin Man
  • 150,910
  • 39
  • 198
  • 279
Alex Pan
  • 3,808
  • 7
  • 28
  • 39
  • 1
    When asking a question we expect to see an example of your effort, either by showing us where you've looked and tried and why those didn't help, or the minimum example of what you've written and an explanation of why it isn't working, along with the minimum necessary supporting input data and expected output. See "[ask]" and "[mcve]". You're asking us to write a tutorial about how to do this, and we have no idea what you know or have tried, resulting in a poorly defined and broad question. – the Tin Man Jan 05 '16 at 21:00
  • 1
    How are we supposed to determine whether to split 100 elements into four or three arrays? – the Tin Man Jan 05 '16 at 21:05

3 Answers3

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

a.group_by.with_index{|_, i| i % 2}.values
# => [[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]]

a.group_by.with_index{|_, i| i % 3}.values
# => [[1, 4, 7, 10], [2, 5, 8], [3, 6, 9]]

a.group_by.with_index{|_, i| i % 4}.values
# => [[1, 5, 9], [2, 6, 10], [3, 7], [4, 8]]

a.group_by.with_index{|_, i| i % 5}.values
# => [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]

a.group_by.with_index{|_, i| i % 6}.values
# => [[1, 7], [2, 8], [3, 9], [4, 10], [5], [6]]
sawa
  • 156,411
  • 36
  • 254
  • 350
  • This will get you N arrays, but it doesn't group contiguous values if that matters to you, e.g. if you wanted the first example to return `[[1,2,3,4,5], [6,7,8,9,10]]`. – nitrogen Jul 22 '20 at 18:05
1

The current top answer by @sawa is useful if you don't need to preserve contiguity of elements. Here's a solution if contiguity matters.

As it happens this problem is vaguely similar to line drawing algorithms and linear interpolation. We basically want to scale original indices to a new range of indices. In other words, we are drawing a line with slope new_count / old_count, starting from the origin.

If it doesn't matter where the larger groups lie in the sequence, and if you don't need empty groups at the end when there aren't enough items, then you can do something simple with Ruby's Enumerable#chunk method:

# This can be copy/pasted into Pry (thus the `self.`)
def self.groups(enum, numgroups)
  enum.chunk.with_index { |_, idx|
    (idx * numgroups / enum.count)
  }.map(&:last)
end

The #chunk method returns indices with the chunks, thus the .map(&:last) gives us just our groups. This is not the most efficient approach if speed matters, but it gets the job done.

Some example runs:

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

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

group2(1..100, 7).to_a
# => [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
 [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
 [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43],
 [44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],
 [59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72],
 [73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86],
 [87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]]

You might have noticed that the longer and shorter chunks are intermixed. One could design a version that computes lengths first, sorts them, and then returns chunks of those sorted sizes.

nitrogen
  • 1,313
  • 1
  • 11
  • 24
0

@sawa's answer does a great job.

But I wrote my own function for the uniform distribution, which the group_by does not give.

My simple function distributes the elements of an array evenly:

def split_array_to_parts(array, parts_number)
  part_size = array.size * 1.0 / parts_number
  partitioned_elements_count = 0

  parts = []
  parts_number.times do |index|
    part_length = (part_size * (index+1)).round - partitioned_elements_count
    parts << array.slice(partitioned_elements_count, part_length)
    partitioned_elements_count += part_length
  end

  parts
end

Let's test them both:

begin
  [11,15,17,21,23].each do |count|
    array = Array.new(count, 0)
    result_array_1 = array.group_by.with_index{|_, i| i % 10}.values
    result_array_2 = split_array_to_parts(array, 10)
    res.map { |inner_array|
      print "#{inner_array.count} "
    }
    print "\n"
  end
end

result_array_1 (group_by.with_index):

2 1 1 1 1 1 1 1 1 1 
2 2 2 2 2 1 1 1 1 1 
2 2 2 2 2 2 2 1 1 1 
3 2 2 2 2 2 2 2 2 2 
3 3 3 2 2 2 2 2 2 2

result_array_2 ( my function split_array_to_parts):

1 1 1 1 2 1 1 1 1 1 
2 1 2 1 2 1 2 1 2 1 
2 1 2 2 2 1 2 2 1 2 
2 2 2 2 3 2 2 2 2 2 
2 3 2 2 3 2 2 2 3 2

You are welcome to check and compare the similar solutions:

How to split (chunk) a Ruby array into parts of X elements? - duplicate

Splitting an array into equal parts in ruby - duplicate

How to chunk an array in Ruby

kli
  • 312
  • 2
  • 8