2011-01-26 44 views
18

Tôi đang tìm kiếm một mảng tương đương String#split trong Ruby Core và ngạc nhiên khi thấy rằng nó không tồn tại. Có một cách thanh lịch hơn sau đây để tách một mảng thành mảng phụ dựa trên một giá trị?Chia mảng thành mảng phụ dựa trên giá trị

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

Sửa: Đối với những người quan tâm, những "thực tế" Vấn đề mà gây ra yêu cầu này có thể được nhìn thấy trong this answer, nơi tôi đã sử dụng @ câu trả lời fd của dưới đây để thực hiện.

+0

Vâng, bằng Python bạn có thể chuyển đổi nó thành một chuỗi (giá trị tách biệt nhau bằng dấu phẩy hoặc một cái gì đó), chia đó, và sau đó quay trở lại vào một danh sách. Dunno nếu đó là một lựa chọn trong Ruby. –

+0

@Rafe Nó sẽ là, nhưng chỉ khi nội dung chỉ là chuỗi. Thậm chí sau đó, điều đó khó có thể được coi là thanh lịch. : p – Phrogz

+0

@Phrogz nếu chúng là số mà nó cũng hoạt động tốt. Bạn chỉ cần làm '','. Tham gia ([str (x) cho x trong list_of_nums])', sau đó chia nhỏ trên bất cứ điều gì, sau đó tham gia lại và chia trên dấu phẩy. Chức năng, vâng, tao nhã, eh không. –

Trả lời

10

Tôi đã cố gắng chơi golf nó một chút, vẫn không phải là một phương pháp duy nhất mặc dù:

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

Hoặc nhanh hơn:

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

Ngoài ra, Enumerable#chunk có vẻ là Ruby 1.9+, nhưng nó rất gần với những gì bạn muốn.

Ví dụ, sản lượng nguyên liệu sẽ là:

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

(Các to_a là làm cho IRB in cái gì đó tốt đẹp, kể từ chunk mang đến cho bạn một Enumerator hơn là một mảng)


Chỉnh sửa: Lưu ý rằng các giải pháp thanh lịch ở trên chậm hơn 2-3 lần so với triển khai nhanh nhất:

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

Rất tuyệt! Tôi đã không nhìn thấy 'chunk' trước đây. Đối với hồ sơ, nó là 1.9.2+, nhưng đó là hoàn toàn chấp nhận được với tôi. – Phrogz

+2

Đây là liên kết đến tài liệu: http://ruby-doc.org/core/classes/Enumerable.html#M001523 –

+0

Không có gì đáng ngạc nhiên (do các lần lặp bổ sung cần thiết cho từ chối/bản đồ) là chậm hơn một chút; Tôi đã thêm một 'câu trả lời' điểm chuẩn thu thập triển khai. – Phrogz

1

Enumerable Các phương pháp khác bạn có thể muốn xem xét là each_slice hoặc each_cons

Tôi không biết nói chung làm thế nào bạn muốn nó được, đây là một cách

>> (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] 
+0

Chỉ dành cho ví dụ mod 3 cụ thể của tôi, nhưng không nói chung. – Phrogz

5

Dưới đây là các tiêu chuẩn tập hợp các câu trả lời (tôi sẽ không được chấp nhận câu trả lời này):

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) 

Việc triển khai đang được thử nghiệm (trên 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 
+0

Một chút muộn quá bên, nhưng: những phương pháp này không hoàn toàn so sánh được, bởi vì kết quả của những phương pháp này không giống nhau. Ba thứ đầu tiên trả về cái gì đó tương tự như những gì 'String # split' trả về (bao gồm cả mảng trống khi hai dấu tách tiếp theo được tìm thấy), trong khi' split_with_chunk' và 'split_with_chunk2' không bao giờ trả về các mảng rỗng và' split_with_chunk3' vẫn chứa giá trị 'grouping' của đoạn. – Confusion

1

đây là một một (với một chuẩn mực so sánh nó với nhanh nhất split_with_each đây 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) 
+0

Điều này giống như mảng # phân vùng, không phải là chuỗi # chia. – Lloeki

14

Somet imes partition là một cách tốt để làm những việc như thế:

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

Không liên quan đến câu hỏi: tác giả muốn tách chuỗi chạy được phân tách. – Lloeki

Các vấn đề liên quan