วันนี้พูดถึง slice_before, slice_when และ slice_after เมธอด super-useful ตอน group items ใน array หรือ enumerable อื่นๆ ตามเกณฑ์ arbitrary
คุณคุ้นเคย Array#slice มันดึง subset ของ array จาก index range:
a = ["a", "b", "c"]
a.slice(1, 2) # => ["b", "c"]
มีประโยชน์แต่ไม่ใช้กับ enumerables ได้ เพราะ enumerables ไม่มี indices
slice_before, slice_when และ slice_after ไม่พึ่ง indices - ใช้กับ enumerable ใดๆ ได้ มาดู!
ใช้ Enumerable#slice_before
Enumerable#slice_before แยก enumerable เป็น groups ที่จุดก่อน match เกิดขึ้น
Match ผ่าน operator === หมายความว่า match ทุกอย่างได้
Value Matches
Match ค่าเดียว ควรชัดเจน :)
a = ["a", "b", "c"]
a.slice_before("b").to_a # => [["a"], ["b", "c"]]
Regular Expression Matches
ใช้ regex สำหรับ textual matching 複雜:
a = ["000", "b", "999"]
a.slice_before(/[a-z]/).to_a # => [["000"], ["b", "999"]]
Range Matches
ทำงานกับตัวเลข slice array ตาม range:
a = [100, 200, 300]
a.slice_before(150..250).to_a # => [[100], [200, 300]]
Class matches
อาจแปลกแต่สอดคล้องกับ === operator:
a = [1, "200", 1.3]
a.slice_before(String).to_a # => [[1], ["200", 1.3]]
ใช้ Block
ถ้าตัวเลือกอื่นไม่พอ ใช้ block หา match programmatically:
a = [1, 2, 3, 4, 5]
a.slice_before do |item|
item % 2 == 0
end
# => [[1], [2, 3], [4, 5]]
ใช้ Enumerable#slice_after
Enumerable#slice_after ทำงานเหมือน slice_before แต่ slice หลัง match ไปเลย :-)
a = ["a", "b", "c"]
a.slice_after("b").to_a # => [["a", "b"], ["c"]]
當然 regex, ranges, blocks ก็ได้ ไม่โชว์ตัวอย่างเพราะ tedious
ใช้ Enumerable#slice_when
Enumerable#slice_when ต่างจาก slice_before และ slice_after แทนที่ match single item match คู่ adjacent items
หมายความว่า group items ตาม "edges" ระหว่างพวกมัน
เช่น group items ตาม "nearness" กับ adjacent items:
a = [1, 2, 3, 100, 101, 102]
# สร้าง group ใหม่เมื่อ difference ระหว่าง adjacent items > 10
a.slice_when do |x, y|
(y - x) > 10
end
# => [[1, 2, 3], [100, 101, 102]]
อยากเรียนรู้เพิ่ม ดู Ruby Docs slice_when มี code examples ดีมาก
Arrays vs Enumerables
ใช้ arrays ในตัวอย่างส่วนใหญ่เพราะเข้าใจง่าย แต่จำได้ว่าใช้ slice_before, slice_when และ slice_after กับ enumerable ใดๆ ได้
เช่น file ที่มี emails มาก ใช้ slice_before แยก emails ตัวต่อตัว (จาก docs):
open("mbox") { |f|
f.slice_before { |line| line.start_with? "From " }.each { |mail| puts mail }
}
สังเกต: slice methods ไม่คืน arrays คืน enumerables หมายความว่า用 map, each และ enumerable methods อื่นๆ ได้ เหมือนกัน split อีกชั้นได้ :)