根据值将数组拆分为子数组

我在Ruby Core中寻找一个与Array相当的String#split ,并且惊讶地发现它不存在。 是否有比以下更优雅的方法将数组基于值拆分为子数组?

 class Array def split( split_on=nil ) inject([[]]) do |a,v| a.tap{ if block_given? ? yield(v) : v==split_on a << [] else a.last < [[1, 2], [4, 5], [7, 8]] #=> [[1, 2], [4, 5], [7, 8], [10]] 

编辑 :对于那些感兴趣的人,可以在这个答案中看到引发这个请求的“真实世界”问题,我在下面用@ fd的答案来实现。

我尝试过打高尔夫球,但仍然不是一个单一的方法:

 (1..9).chunk{|i|i%3==0}.reject{|sep,ans| sep}.map{|sep,ans| ans} 

或更快:

 (1..9).chunk{|i|i%3==0 || nil}.map{|sep,ans| sep&&ans}.compact 

此外, Enumerable#chunk似乎是Ruby 1.9+,但它非常接近你想要的。

例如,原始输出将是:

 (1..9).chunk{ |i|i%3==0 }.to_a => [[false, [1, 2]], [true, [3]], [false, [4, 5]], [true, [6]], [false, [7, 8]], [true, [9]]] 

to_a是使irb打印出一些不错的东西,因为chunk为你提供了一个枚举器而不是一个数组)


编辑 :请注意,上述优雅的解决方案比最快的实施方案慢2-3倍:

 module Enumerable def split_by result = [a=[]] each{ |o| yield(o) ? (result << a=[]) : (a << o) } result.pop if a.empty? result end end 

有时分区是做这样的事情的好方法:

 (1..6).partition { |v| v.even? } #=> [[2, 4, 6], [1, 3, 5]] 

以下是汇总答案的基准(我不会接受这个答案):

 require 'benchmark' a = *(1..5000); N = 1000 Benchmark.bmbm do |x| %w[ split_with_inject split_with_inject_no_tap split_with_each split_with_chunk split_with_chunk2 split_with_chunk3 ].each do |method| x.report( method ){ N.times{ a.send(method){ |i| i%3==0 || i%5==0 } } } end end #=> user system total real #=> split_with_inject 1.857000 0.015000 1.872000 ( 1.879188) #=> split_with_inject_no_tap 1.357000 0.000000 1.357000 ( 1.353135) #=> split_with_each 1.123000 0.000000 1.123000 ( 1.123113) #=> split_with_chunk 3.962000 0.000000 3.962000 ( 3.984398) #=> split_with_chunk2 3.682000 0.000000 3.682000 ( 3.687369) #=> split_with_chunk3 2.278000 0.000000 2.278000 ( 2.281228) 

正在测试的实现(在Ruby 1.9.2上):

 class Array def split_with_inject inject([[]]) do |a,v| a.tap{ yield(v) ? (a << []) : (a.last << v) } end.tap{ |a| a.pop if a.last.empty? } end def split_with_inject_no_tap result = inject([[]]) do |a,v| yield(v) ? (a << []) : (a.last << v) a end result.pop if result.last.empty? result end def split_with_each result = [a=[]] each{ |o| yield(o) ? (result << a=[]) : (a << o) } result.pop if a.empty? result end def split_with_chunk chunk{ |o| !!yield(o) }.reject{ |b,a| b }.map{ |b,a| a } end def split_with_chunk2 chunk{ |o| !!yield(o) }.map{ |b,a| b ? nil : a }.compact end def split_with_chunk3 chunk{ |o| yield(o) || nil }.map{ |b,a| b && a }.compact end end 

您可能要考虑的其他可枚举方法是each_slice或each_cons

我不知道你想要它有多普遍,这是一种方式

 >> (1..9).each_slice(3) {|a| p a.size>1?a[0..-2]:a} [1, 2] [4, 5] [7, 8] => nil >> (1..10).each_slice(3) {|a| p a.size>1?a[0..-2]:a} [1, 2] [4, 5] [7, 8] [10] 

这是另一个(基准比较它与最快的split_with_each这里https://stackoverflow.com/a/4801483/410102 ):

 require 'benchmark' class Array def split_with_each result = [a=[]] each{ |o| yield(o) ? (result << a=[]) : (a << o) } result.pop if a.empty? result end def split_with_each_2 u, v = [], [] each{ |x| (yield x) ? (u << x) : (v << x) } [u, v] end end a = *(1..5000); N = 1000 Benchmark.bmbm do |x| %w[ split_with_each split_with_each_2 ].each do |method| x.report( method ){ N.times{ a.send(method){ |i| i%3==0 || i%5==0 } } } end end user system total real split_with_each 2.730000 0.000000 2.730000 ( 2.742135) split_with_each_2 2.270000 0.040000 2.310000 ( 2.309600)