Tôi nghĩ sẽ vui nếu tiếp theo bài hôm qua về regex conditionals bằng cách xem vài trick neat khác bạn có thể làm với regular expressions trong Ruby.
Chia string via regular expression
Bạn có thể khá quen với split string dùng text delimiter:
"one,two".split(",") # => ["one", "two"]
Nhưng bạn có biết split cũng chấp nhận regular expression?
# dùng `,` và `-` làm delimiters "one,two-three".split(/,|-/) => ["one", "two", "three"] # Dùng comma làm thousands separator cho US currency, # nhưng dùng space cho Euros "1,000USD".split /(?=.*(USD))(?(1),| )/
Capture delimiters
Đây là trick party neat. Thông thường, khi split string, delimiters biến mất:
# Dấu phẩy biến mất! "one,two".split(",") # => ["one", "two"]
Nhưng nếu bạn dùng regular expression và đặt delimiter bên trong group, split sẽ capture delimiter cũng như nhau.
"one,two-three".split(/(,|-)/) => ["one", ",", "two", "-", "three"]
Lý do là split thực sự split string tại boundary của mỗi capture group.
Lạm dụng split
Bạn có thể lạm dụng split để nó hành xử gần như match. Trong code dưới, tôi dùng bốn group trong regex để split string thành 4 mảnh.
"1-800-555-1212".split(/(1)-(\d{3})-(\d{3})-(\d{4})/) => ["", "1", "800", "555", "1212"]
Item đầu trong array kết quả là empty string vì regex match toàn bộ source string.
Global matching
Mặc định, regular expressions chỉ match pattern một lần. Trong code dưới, chúng ta chỉ nhận một match dù có năm match có thể.
"12345".match /\d/ => #<MatchData "1">
Trong ngôn ngữ khác như Perl, giải pháp là flag regex là "global". Ruby không có option đó, nhưng có phương thức String#scan.
Phương thức scan trả về array chứa tất cả matches:
"12345".scan /\d/ => ["1", "2", "3", "4", "5"]
Nó thậm chí có block syntax tiện lợi:
"12345".scan /\d/ do |i| puts i end
Thật đáng tiếc, không có cách nào scan string lazily. Vì vậy kỹ thuật này có thể không phù hợp cho - ví dụ - xử lý file 500mb.
Scanning với groups
Lúc này tôi hy vọng bạn tò mò trick kỳ lạ nào ta làm được bằng groups trong scan.
Thật đáng tiếc, behavior ở đây hoàn toàn predictable và CHÁN. Groups dẫn đến multi-dimensional array:
"hiho hiho".scan /(hi)(ho)/ => [["hi", "ho"], ["hi", "ho"]]
Có một edge case kỳ lạ. Nếu dùng groups, bất cứ thứ gì KHÔNG trong group sẽ không được trả về.
"hiho hiho".scan /(hi)ho/ => [["hi"], ["hi"]]
Shorthand
Tôi cá bạn biết =~ để check regex có match string không. Nó trả về index của character bắt đầu match.
"hiho" =~ /hi/ # 0 "hiho" =~ /ho/ # 2
Có cách nhanh khác check match. Tôi nói về toán tử ===.
/hi/ === "hiho" # true
Khi viết a === b trong Ruby, ta hỏi "có phải b thuộc set được định nghĩa bởi a không". Hoặc trường hợp này, "có phải 'hiho' thuộc set string match regex /hi/ không".
Toán tử === được dùng nội bộ trong case statements của Ruby. Nghĩa là bạn có thể dùng regular expressions trong case statements.
case "hiho" when /hi/ puts "match" end
Triple-equals operator cũng hữu ích cho phép method của bạn chấp nhận cả regular expressions hoặc strings.
Tưởng tượng bạn muốn execute code khi error xảy ra, trừ khi nó nằm trong list class pre-configured để ignore. Trong ví dụ dưới, dùng === operator cho phép user chỉ định string hoặc regex.
def ignored?(error) @ignored_patterns.any? { |x| x === error.class.name } end
Đó là xong!
Chắc chắn, có chục trick nhỏ như này rải rác khắp Ruby và Rails. Nếu bạn có trick đặc biệt yêu thích, cho tôi biết!