12/08/2018, 13:06

Các thủ thuật với Regular Expression

Regular Repression (regex) là một công cụ mạnh trong việc xử lý xâu. Sử dụng regex sẽ giúp tránh được việc phải viết các hàm kiểm tra, các câu điều kiện dài. Nhờ thế, regex đặc biệt phù hợp vói Ruby, một ngôn ngữ có cú pháp ngắn gọn. Sau đây là một số thủ thuật sử dụng regex trong code Ruby để làm ...

Regular Repression (regex) là một công cụ mạnh trong việc xử lý xâu. Sử dụng regex sẽ giúp tránh được việc phải viết các hàm kiểm tra, các câu điều kiện dài. Nhờ thế, regex đặc biệt phù hợp vói Ruby, một ngôn ngữ có cú pháp ngắn gọn. Sau đây là một số thủ thuật sử dụng regex trong code Ruby để làm cho code của bạn còn ngắn gọn và sáng sủa hơn nữa.

Mọi Ruby coder đều biết cách sử dụng hàm split như sau:

"one,two".split ","
=> ["one", "two"]

Nhưng không phải ai cũng biết rằng hàm split này nhận cả regex

"one,two-three".split /,|-/
=> ["one", "two", "three"]

Thậm chí, ta có thể tách xâu và lấy luôn cả các kí hiệu phân cách vào trong dãy mới sinh ra, một việc dường như không tưởng với hàm split. Tất cả công việc cần làm là nhóm các kí hiệu phân cách lại với nhau:

"one,two-three".split /(,|-)/
=> ["one", ",", "two", "-", "three"]

Lý do bởi vì thực ra hàm split được cài đặt để cắt xâu ở biên của mỗi nhóm mà nó bắt được.

Dựa vào đặc tính trên, ta có thể khiến cho hàm split hoạt động gần giống như match.

"1-800-555-1212".split /(1)-(d{3})-(d{3})-(d{4})/
=> ["", "1", "800", "555", "1212"]

Bình thường hàm match chỉ cho ra kết quả đầu tiên tìm được:

"12345".match /d/
=> #<MatchData "1">

Để cho ra tất cả các kết quả tìm được, ta có thể dùng hàm scan:

"12345".scan /d/
=> ["1", "2", "3", "4", "5"]

Ngoài ra, ta có thể dùng toán tử === để match:

/hi/ === "hiho"
# true

Toán tử === được cài đặt sử dụng trong cấu trúc case của Ruby, vì vậy ta hoàn toàn có thể sử dụng regex trong cấu trúc này:

case "hiho"
when /hi/
  puts "match"
end

Toán tử =~ cũng là một cách để kiểm tra match. Kết quả tra về là số thứ tự xuất hiện lần đầu của regex:

"hiho" =~ /hi/
# 0
"hiho" =~ /ho/
# 2

Sử dụng nhiều nhóm trong regex để match được nhiều kết quả hơn:

m = "Framgia, Vietnam".match /([^,]+)(, *)([A-Z])/
# => #<MatchData "Framgia, Vietnam" 1:"Framgia" 2:", " 3:"Vietnam">
m[1]
# Framgia
m[3]
# Vietnam

Ta có thể đặt tên cho các nhóm trong regex để giúp cho kết quả hàm match được "đẹp" hơn, dễ dàng cho việc truy cập và xử lý. Cú pháp đặt tên nhóm như sau:

/(?<groupname>regex)/

Ví dụ:

m = "Framgia, Vietnam".match /(?<company>[^,]+), *(?<country>[A-Z])/
# => #<MatchData "Framgia, Vietnam" company:"Framgia" country:"Vietnam">
m[:company]
# Framgia
m[:country]
# Vietnam

Kết quả thu được có thể sử dụng như một hash.

Biểu thức điều kiện trong regex có dạng /(?(A)X|Y)/, trong đó A là điều kiện, X là kết quả nếu A đúng, Y là kết quả nếu A sai. Biểu thức có thể khuyết X hoặc Y. Ví dụ:

re = /^(1-)?(?(1)d{3}-|(d{3}-)?)d{3}-d{4}/

"1-800-555-1212".match re
#=> #<MatchData "1-800-555-1212" 1:"1-" 2:nil>

"800-555-1212".match re
#=> #<MatchData "800-555-1212" 1:nil 2:"800-">

"555-1212".match re
#=> #<MatchData "555-1212" 1:nil 2:nil>

"1-555-1212".match re
=> nil

Regex re ở ví dụ trên nhìn qua trông có vẻ rất phức tạp, nhưng rất dễ hiểu nếu ta chia nhỏ ra thành từng phần:

  • ^(1-)?: Kiểm tra xâu có bắt đầu với "1-" không. Nếu có đưa vào nhóm 1
  • (?(1): Biểu thức điều kiện kiểm tra xâu có trong nhóm 1 không
  • d{3}-: Nếu xâu nằm trong nhóm 1, kiểm tra tiếp theo có phải là 3 chữ số và dấu "-" không
  • |(d{3}-)?: Nếu không nằm trong nhóm 1, kiểm tra xem xâu có bắt đầu bằng 3 chữ số và dấu "-" không
  • d{3}-d{4}: Kiểm tra phần tiếp theo có phải là 3 chữ số, dấu "-" và 4 chữ số không, phần này hoạt động như regex bình thường
0