Greedy and lazy matching in regex quantifiers

NhaxathyNhaxathy
5 min read

Regex quantifiers

Regex quantifiers dịch sang tiếng Việt có thể hiểu là toán tử lặp hoặc bộ định lượng nhằm xác định hoặc cho phép lặp lại ký tự hoặc nhóm ký tự một số lần nhất định

Các loại Regex quantifiers gồm:

QuantifiersMô tả
*0 hoặc nhiều lần
+1 hoặc nhiều lần
?0 hoặc 1 lần
{n}chính xác n lần
{n,}n hoặc nhiều hơn
{n,m}từ n đến m lần

Các quantifiers này mặc định là greedy matching (tham lam), tức là chúng sẽ cố gắng matching nhiều ký tự nhất có thể. Ví dụ, ta có 1 đoạn text greeting: "Hello World!"

Muốn tìm nội dung được đặt trong cặp dấu double quotes (““), ta có thể sử dụng quantifier * như sau ".*"

Tuy nhiên trong một số ngữ cảnh, ta cần những tìm kiếm để phù hợp với đúng mục đích công việc. Giả sử ta có đoạn JSON object sau. Làm thế nào để lấy được các key của object?

{
    "name": "LitteNotes",
    "address": "Hanoi, VietNam",
    "job": "IT"
}

Lúc này nếu vẫn sử dụng regex “.*” như trên ta sẽ thu được toàn bộ object chứ không phải mỗi key.

Do đó, đôi khi ta không thể tham lam được :D

Greedy search

Giải thích một cách đơn giản thì greedy search sẽ cố gắng match nhiều nhất có thể, nếu không khớp nó sẽ backtrack và match ít hơn từng chút một đến khi match toàn bộ pattern.

    // Dấu , ở cuối câu nên regex ".*" không match
    "name": "LitteNotes",
    \___________________/
    ".*" not matched

    // Nó sẽ backtrack và match ít hơn, đến lúc này đã match được
    "name": "LitteNotes",
    \__________________/
    ".*" matched

Lazy (non-greedy) search

Lazy search thì ngược với greedy, nó sẽ cố gắng match ít nhất có thể.

Nó sẽ tìm kiếm từ đầu chuỗi, match từng pattern phù hợp và tiếp tục tìm kiếm tới khi kết thúc

Để enable lazy search, ta cần thêm dấu ? vào sau các quantifier.

    "name": "LitteNotes",
    \____/   \_________/
    ".*?"    ".*?"

Vậy ta thấy với đoạn text là "name": "LitteNotes", khi tìm kiếm phần nằm giữa 2 dấu double quotes (““)

  • Greedy(".*") → "name": "LitteNotes"

  • Lazy(".*?") → "name""LitteNotes"

Quay lại bài toán lúc đầu cần tìm kiếm các key của JSON object và thêm suffix “_en” vào từng key

Ta có thể sử dụng lazy search và replace như sau:

Search pattern: "(.*?)"(:). Lưu ý, pattern gốc sẽ là ".*?":, dấu () dùng để ghi nhớ biến

  • .*? lazy search

  • : dấu : giữa key và value

Replacement pattern: "$1_en"$2

  • $1 biến lấy giá trị từ các key trong () ở trên

  • $2 chính là dấu : (có thể fix cứng là : cũng được)

Sau khi replace ta có kết quả:

Sử dụng greedy và lazy matching đối các regex quantifier sẽ giúp chúng ta tìm kiếm và thay thế các chuỗi ký tự hiệu quả hơn. Tuy nhiên, đôi khi có nhiều trường hợp cả greedy và lazy matching đều không sử dụng được và ta phải tìm giải pháp khác.

Trở lại với bài toán trên, tìm kiếm các key của JSON object và sửa thêm suffix “_en”. Ngoài cách sử dụng lazy matching, ta có thể sử dụng search pattern "[^"]+":. Pattern này chứa [^"]+ bao gồm 1 hoặc nhiều ký tự khác dấu double quotes (“) và trả ra kết quả tương đương lazy search ".*?":

Mở rộng bài toán hơn với yêu cầu file JSON giờ chứa rất nhiều dữ liệu và kích thước trở nên lớn dần. Để giảm size của file, ta không format JSON dưới dạng pretty như trên nữa mà phải loại bỏ các ký tự space để tạo thành 1 line duy nhất. Đồng thời tăng thêm 1 field “country” được lấy từ field “address” ra, field “address” được update lại xóa giá trị “VietNam” đi chỉ giữ lại tên thành phố. Tuy nhiên có một số object vẫn chưa được update field “address”, ta cần detect ra và update lại.

Cụ thể như sau:

// Dạng pretty
{
    "name": "LitteNotes",
    "address": "Hanoi, VietNam", // Trường này chưa được update, cần detect ra và update
    "country": "VietNam",
    "job": "IT"
},
{
    "name": "Little",
    "address": "Hanam",
    "country": "VietNam",
    "job": "IT"
}
// Dạng normal, cần tìm kiếm và update ở đây
{"name": "LitteNotes", "address": "Hanoi, VietNam","country": "VietNam", "job": "IT"}, {"name": "Little","address": "Hanam", "country": "VietNam", "job": "IT"}

Sử dụng các pattern tìm kiếm ở trên, ta thử xem các kết quả sẽ như nào

  • Greedy "address": ".*VietNam"

    Rõ ràng, kết quả này không thể sử dụng được

  • Lazy "address": ".*?VietNam"

    Kết quả này chứa cả “country” cho nên cũng không sử dụng được

  • Other "address": "[^"]+VietNam"

    Lúc này ta tìm được 1 object có field “address” chưa được update và giải quyết được bài toán

Summary

  • Greedy: Mặc định các regex quantifiers sẽ sử dụng greedy matching, và chúng cố gắng match nhiều nhất có thể

  • Lazy (non-greedy): Được enable bằng cách thêm ? vào sau các quantifier khác, và cố gắng match ít nhất có thể

    Có một cách không sử dụng lazy matching và cho kết quả tương tự lazy matching, cách này không phải là thay thế cho lazy matching. Nó chỉ đơn giản là một logic khác. Việc sử dụng các search pattern nào phù hợp tùy thuộc vào các bài toán tìm kiếm cụ thể

0
Subscribe to my newsletter

Read articles from Nhaxathy directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nhaxathy
Nhaxathy