Hôm nay chúng ta nói về slice_before, slice_when và slice_after. Đây là phương thức siêu hữu ích khi cần nhóm item trong array hoặc enumerable khác dựa trên tiêu chí tùy ý.
Bạn có thể quen với Array#slice. Nó cho phép lấy subset của array dựa trên range index:
a = ["a", "b", "c"] a.slice(1, 2) # => ["b", "c"]
Điều này hữu ích, nhưng không dùng được với enumerable, vì enumerable không có index.
Các phương thức slice_before, slice_when và slice_after không phụ thuộc index - nên dùng được với bất kỳ enumerable nào. Hãy xem!
Dùng Enumerable#slice_before
Enumerable#slice_before chia enumerable thành các nhóm tại điểm TRƯỚC khi match xảy ra.
Match được thực hiện qua toán tử ===, nghĩa là bạn có thể match mọi thứ.
Khớp giá trị (Value Matches)
Bạn có thể match một giá trị đơn lẻ. Điều đó hiển nhiên. :)
a = ["a", "b", "c"] a.slice_before("b").to_a # => [["a"], ["b", "c"]]
Khớp Regular Expression
Bạn có thể dùng regular expression cho textual matching phức tạp hơn.
a = ["000", "b", "999"] a.slice_before(/[a-z]/).to_a # => [["000"], ["b", "999"]]
Khớp Range
Nếu làm việc với số, bạn có thể slice array dựa trên range.
a = [100, 200, 300] a.slice_before(150..250).to_a # => [[100], [200, 300]]
Khớp Class
Cái này có vẻ hơi lạ, nhưng hoàn toàn phù hợp với hành vi của toán tử ===.
a = [1, "200", 1.3] a.slice_before(String).to_a # => [[1], ["200", 1.3]]
Dùng block
Nếu không option nào đủ linh hoạt, bạn luôn có thể tìm match programmatically với block.
a = [1, 2, 3, 4, 5] a.slice_before do |item| item % 2 == 0 end # => [[1], [2, 3], [4, 5]]
Dùng Enumerable#slice_after
Enumerable#slice_after hoạt động chính xác như Enumerable#slice_before trừ việc slice xảy ra SAU khi match. Dễ hiểu. :-)
a = ["a", "b", "c"] a.slice_after("b").to_a # => [["a", "b"], ["c"]]
Tất nhiên, bạn có thể match bằng regex, range, và block. Tôi không show ví dụ vì sẽ nhàm chán.
Dùng Enumerable#slice_when
Enumerable#slice_when là con thú khác so với slice_before và slice_after. Thay vì match single item trong array, bạn match một cặp item liền kề.
Điều này nghĩa là bạn có thể nhóm item dựa trên "cạnh" (edges) giữa chúng.
Ví dụ, ở đây chúng ta nhóm item dựa trên "gần nhau" (nearness) với item liền kề.
a = [1, 2, 3, 100, 101, 102] # Tạo nhóm mới khi hiệu giữa # hai item liền kề > 10. a.slice_when do |x, y| (y - x) > 10 end # => [[1, 2, 3], [100, 101, 102]]
Nếu quan tâm tìm hiểu thêm, xem Ruby Docs cho slice_when. Có vài ví dụ code hay.
Arrays vs Enumerables
Tôi dùng array trong hầu hết ví dụ trên vì array dễ hiểu. Nhưng hãy nhớ bạn có thể dùng slice_before, slice_when và slice_after với bất kỳ enumerable nào.
Ví dụ, nếu bạn có file chứa nhiều email, bạn có thể tách từng email dùng slice_before. Code dưới lấy từ docs.
open("mbox") { |f| f.slice_before { |line| line.start_with? "From " }.each { |mail| puts mail }
Và hãy để ý slice methods không trả về array. Chúng trả về enumerable. Nghĩa là bạn có thể dùng map, each và mọi enumerable method yêu thích. Chết, bạn có thể thậm chí làm split khác nữa. :)