2011-01-26 84 views
18

我在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 << v 
     end 
     } 
    end.tap{ |a| a.pop if a.last.empty? } 
    end 
end 

p (1..9).to_a.split{ |i| i%3==0 }, 
    (1..10).to_a.split{ |i| i%3==0 } 
#=> [[1, 2], [4, 5], [7, 8]] 
#=> [[1, 2], [4, 5], [7, 8], [10]] 

编辑:对于那些有兴趣,这引发了此请求的“真实世界”的问题可以在this answer,在那里我用@ FD的回答下面的实施可以看出。

+0

好了,在Python,你可以把它转换成字符串(用逗号或东西分隔值),拆分,然后返回到列表中。不知道,如果这是Ruby中的一个选项。 – 2011-01-26 00:24:08

+0

@Rafe它会,但只有当内容只是字符串。即使那样,那也不算高雅。 :p – Phrogz 2011-01-26 00:38:45

+0

@Progrog如果他们是数字,它也可以很好地工作。你只需要做'','join'(list_of_nums中的[str(x)]],然后分割,然后重新加入并在逗号分割。功能,是的,优雅,呃没有。 – 2011-01-26 00:45:45

回答

10

我试着打高尔夫球了一点,仍然不是一个单一的,虽然方法:

(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

你可能要考虑其他Enumerable方法是each_sliceeach_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] 
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 
1

这里是另一个一个(与基准比较它以最快的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) 
14

舒美特输入法partition是做这样的事情的好办法:

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