将Ruby数组的元素分成精确数量的(几乎)相等大小的子数组

74

我需要一种方法将一个数组分成粗略相等大小的确切数量的小数组。有人知道如何做到这一点吗?

例如:

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.
在这个问题中,所需的块数事先指定,并且每个块的大小最多相差1。

9
很不幸,所选的示例在“分成3组”和“以3个元素为一组分组”的情况下产生了相同的结果,这就是你得到两个完全不同答案的原因。 - tokland
1
这个问题与链接的问题不同。链接的问题是将一个数组分成相等的、已知的大小;而这个问题是将一个数组分成相等数量的块,每个块的大小相似。 - Barry Kelly
1
同意:这不是链接问题的重复,这是搜索“ruby slice array into n equal parts”的顶部结果。以下是我对另一个问题提出的仅使用Ruby(不需要Rails)的答案:https://stackoverflow.com/a/63040779 - nitrogen
5个回答

136

你正在寻找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]]

40
注意:这将把数组分成大小为3的组,而不是分成三个相等大小的组。 - yasith
如果数组大小不能被切片数整除,是否可以将剩余的切片与前一个切片合并?以你的例子为例,[6, 7] 将与 [3, 4, 5] 合并成 [3, 4, 5, 6, 7] - Mohamad
1
@BorisStitnicky 这可能说明文档网站的用户体验不佳。有时,按照你想要做什么来搜索比知道哪个类具有你想要的方法更容易。谷歌搜索是关键,如果结果是 Stack Overflow,那就是它了。有些新手以及不熟悉某种语言的程序员也会使用这种方式。技能是一个金字塔,新手会比专家更多。 - ahnbizcad

124

也许我误解了问题,因为其他答案已经被接受了,但是从问题的描述来看,你好像想要将数组分成三组相等的部分,而不管每个组的大小,而不是像之前的答案一样将其分成N个由3个元素组成的组。如果这是你想要的,Rails(ActiveSupport)也有一个名为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]]

我认为没有 Ruby 的等效方法,但是你可以通过添加这个简单的方法获得大致相同的结果:

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

唯一的区别(您可以看到)是这不会将“空白空间”扩展到所有组中; 每个组除了最后一个都是相等大小的,最后一个组始终容纳余数加上所有的“空白空间”。

更新:正如@rimsky所指出的那样,上面的方法并不总是会产生正确数量的组(有时会在结尾创建多个“空组”,并将它们留下)。以下是从ActiveSupport的定义简化而来的更新版本,它将额外的元素分散开来填补所需的组数。

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

4
我知道这是一篇旧文章,但对于那些考虑上面的 Ruby 等效方法的人来说,它并不完全正确。如果你尝试将一个包含20个元素的数组分成11组,你最终只会得到10组。slice_size将为2,而20可被2整除。 - rimsky
这就是我来这里寻找的东西。不是像被接受的答案那样按n大小分组。谢谢。 - Jeff Zivkovic
不错的发现 @rimsky!已更新 ;) - mltsy
看起来@rimsky的评论不再正确,至少在ActiveSupport 4.1.16上。对一个包含20个元素的数组进行操作是可行的:[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
ActiveSupport版本一直可用。@rimsky指出我之前的(简化)方法有这个缺陷。 - mltsy

16

尝试

a.in_groups_of(3,false)

它将完成你的任务


19
请注意,in_groups_of 是特定于Rails(或更确切地说是ActiveSupport),而@Joshua的答案可在Ruby中任何地方使用。尽管如此,他提供了一个可行的解决方案,值得点赞。 - Phrogz
它也只在Array上。 - Joshua Cheek
请注意,第二个参数是填充值(默认为nil),以防数组大小不能被第一个参数整除。 - thisismydesign

5

如 mltsy 所写,in_groups(n, false) 应该可以完成任务。

我只想添加一个小技巧来获得正确的平衡 my_array.in_group(my_array.size.quo(max_size).ceil, false)

这里有一个例子来说明这个诀窍:

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

3
这需要更好的智能化处理来清除多余的部分,但这是一个合理的开始。
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]] 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接