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.