Contents
Chào mừng bạn đến với Fx Studio. Chủ đề bài viết lần này là Regular Expression (hay còn gọi là Regex) trong Swift. Tên dân dev hay gọi là Biểu thức chính quy. Đây là một trong những khái niệm lý thuyết cơ bản & kinh điển. Áp dụng vào hầu hết các ngôn ngữ lập trình. Giúp bạn tăng khả năng xử lý chuỗi & text một cách chuyên nghiệp.
Còn nếu mọi việc đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
Đây là một khái niệm thuần lý thuyết & ở mức cơ bản của lập trình. Nên bạn không cần chuẩn bị gì nhiều. Tất nhiên, bạn cần nắm vững ngôn ngữ Swift trước nhóe.
Đọc thêm về Basic Swift trong 10 phút.
Regular Expression (Regex) là gì?
Khái niệm
Regular Expression là biểu thức chính quy hay biểu thức định lý. Là một chuỗi ký tự đặc biệt giúp mô tả một mẫu tìm kiếm trong văn bản.
Biểu thức chính quy rất mạnh mẽ và linh hoạt, được sử dụng rộng rãi trong lập trình và xử lý văn bản. Dưới đây là một số ứng dụng phổ biến của Regular Expression:
- Tìm kiếm và thay thế: Regular Expression thường được sử dụng để tìm kiếm hoặc thay thế các chuỗi ký tự phù hợp với một mẫu nhất định trong văn bản.
- Kiểm tra đầu vào: Regular Expression có thể được sử dụng để kiểm tra xem một chuỗi ký tự có phù hợp với một định dạng nhất định hay không, chẳng hạn như kiểm tra xem một chuỗi có phải là một địa chỉ email hợp lệ hay không.
- Phân tách chuỗi: Regular Expression có thể được sử dụng để phân tách một chuỗi ký tự thành các phần nhỏ hơn dựa trên một hoặc nhiều ký tự phân cách.
Regular Expression có cú pháp phức tạp và đòi hỏi thời gian để học và làm quen, nhưng nó rất mạnh mẽ và linh hoạt, và có thể giúp giải quyết nhiều vấn đề phức tạp liên quan đến xử lý chuỗi.
Regex trong Swift
Trong Swift, bạn có thể sử dụng lớp NSRegularExpression
để làm việc với biểu thức chính quy. Dưới đây là một ví dụ về cách sử dụng biểu thức chính quy để tìm kiếm một chuỗi trong Swift:
import Foundation let string = "Hello, World!" let pattern = "Hello, \\w+!" do { let regex = try NSRegularExpression(pattern: pattern) let results = regex.matches(in: string, range: NSRange(string.startIndex..., in: string)) for match in results { let range = match.range if let swiftRange = Range(range, in: string) { let match = string[swiftRange] print("Matched string: \(match)") } } } catch let error { print("Invalid regex: \(error.localizedDescription)") }
Trong đoạn mã trên,
NSRegularExpression(pattern: pattern)
tạo một đối tượng biểu thức chính quy từ một mẫu (pattern
).regex.matches(in:range:)
sau đó được sử dụng để tìm tất cả các kết quả phù hợp trong chuỗi đầu vào.- Mỗi kết quả phù hợp là một đối tượng
NSTextCheckingResult
chứa một phạm vi (range
) chỉ định vị trí của kết quả phù hợp trong chuỗi đầu vào.
Các trường hợp sử dụng cơ bản
Dưới đây là một số trường hợp sử dụng cơ bản của biểu thức chính quy (Regex) trong Swift:
- Tìm kiếm: Tìm kiếm một mẫu trong một chuỗi.
let string = "Hello, World!" let pattern = "Hello, \\w+!" let regex = try NSRegularExpression(pattern: pattern) let range = NSRange(location: 0, length: string.utf16.count) let match = regex.firstMatch(in: string, options: [], range: range)
- Thay thế: Thay thế tất cả các kết quả phù hợp với một mẫu trong một chuỗi.
let string = "Hello, World!" let pattern = "\\w+" var newString = string do { let regex = try NSRegularExpression(pattern: pattern) let range = NSRange(location: 0, length: string.utf16.count) newString = regex.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: "Word") } catch let error { print("Invalid regex: \(error.localizedDescription)") } print(newString)
- Kiểm tra đầu vào: Kiểm tra xem một chuỗi có phù hợp với một mẫu nhất định hay không.
let string = "123-45-6789" let pattern = "^\\d{3}-\\d{2}-\\d{4}$" let regex = try NSRegularExpression(pattern: pattern) let range = NSRange(location: 0, length: string.utf16.count) let match = regex.firstMatch(in: string, options: [], range: range) let isValid = match != nil
- Phân tách chuỗi: Phân tách một chuỗi thành các phần nhỏ hơn dựa trên một mẫu.
let string = "one,two,three" let pattern = "," let components = string.components(separatedBy: pattern)
Lưu ý rằng trong tất cả các ví dụ trên, bạn cần bắt lỗi khi tạo một đối tượng NSRegularExpression
vì mẫu có thể không hợp lệ.
Ý nghĩa các Regex cú pháp được sử dụng
Biểu thức chính quy (Regular Expression) trong Swift sử dụng cú pháp tương tự như nhiều ngôn ngữ lập trình khác. Dưới đây là một số cú pháp cơ bản, được phân theo nhóm:
Ký tự đơn
.
: Khớp với bất kỳ ký tự nào ngoại trừ dòng mới.\d
: Khớp với bất kỳ chữ số nào (tương đương[0-9]
).\D
: Khớp với bất kỳ ký tự không phải chữ số nào (tương đương[^0-9]
).\w
: Khớp với bất kỳ ký tự từ a-z, A-Z, 0-9 hoặc _.\W
: Khớp với bất kỳ ký tự không phải từ a-z, A-Z, 0-9 hoặc _.
Nhóm ký tự
[abc]
: Khớp với bất kỳ ký tự nào trong nhóm (ở đây là a, b, hoặc c).[^abc]
: Khớp với bất kỳ ký tự nào không nằm trong nhóm (ở đây là không phải a, b, hoặc c).
Lượng tử
*
: Khớp với 0 hoặc nhiều lần ký tự hoặc nhóm ký tự trước nó.+
: Khớp với 1 hoặc nhiều lần ký tự hoặc nhóm ký tự trước nó.?
: Khớp với 0 hoặc 1 lần ký tự hoặc nhóm ký tự trước nó.{n}
: Khớp với n lần ký tự hoặc nhóm ký tự trước nó.{n,}
: Khớp với ít nhất n lần ký tự hoặc nhóm ký tự trước nó.{n,m}
: Khớp với ít nhất n lần và nhiều nhất m lần ký tự hoặc nhóm ký tự trước nó.
Vị trí
^
: Khớp với bắt đầu của một chuỗi.$
: Khớp với kết thúc của một chuỗi.
Nhóm
(abc)
: Tạo một nhóm và lưu lại kết quả khớp để sử dụng sau.
Lưu ý rằng cú pháp trên có thể khác nhau tùy thuộc vào cờ (flags) được sử dụng khi tạo biểu thức chính quy. Ví dụ, cờ i
biểu thị không phân biệt chữ hoa chữ thường, trong khi cờ m
biểu thị chế độ nhiều dòng, trong đó ^
và $
khớp với bắt đầu và kết thúc của mỗi dòng, không chỉ là toàn bộ chuỗi.
Ví dụ áp dụng
Dưới đây là một ví dụ về cách sử dụng biểu thức chính quy (Regex) để kiểm tra xem một chuỗi có phải là định dạng email hợp lệ hay không trong Swift:
import Foundation let email = "test@example.com" let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" let regex = try NSRegularExpression(pattern: pattern) let range = NSRange(location: 0, length: email.utf16.count) let isValidEmail = regex.firstMatch(in: email, options: [], range: range) != nil print(isValidEmail) // prints: true
Trong đoạn mã trên, pattern
là một biểu thức chính quy mô tả định dạng email hợp lệ. Với mẫu là:
"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
Dưới đây là giải thích chi tiết:
[A-Z0-9a-z._%+-]+
: Phần này khớp với một hoặc nhiều ký tự trong nhóm. Nhóm này bao gồm các chữ cái viết hoa từ A-Z, các chữ số từ 0-9, các chữ cái viết thường từ a-z, dấu chấm (.), dấu gạch dưới (_), dấu phần trăm (%), dấu cộng (+) và dấu gạch ngang (-). Đây là phần “tên người dùng” của địa chỉ email.@
: Phần này khớp với ký tự @. Đây là ký tự phân tách giữa tên người dùng và tên miền trong địa chỉ email.[A-Za-z0-9.-]+
: Phần này khớp với một hoặc nhiều ký tự trong nhóm. Nhóm này bao gồm các chữ cái viết hoa từ A-Z, các chữ số từ 0-9, các chữ cái viết thường từ a-z, dấu chấm (.) và dấu gạch ngang (-). Đây là phần “tên miền” của địa chỉ email.\\.
: Phần này khớp với ký tự dấu chấm (.). Đây là ký tự phân tách giữa tên miền và phần mở rộng của tên miền trong địa chỉ email.[A-Za-z]{2,64}
: Phần này khớp với từ 2 đến 64 ký tự trong nhóm. Nhóm này bao gồm các chữ cái viết hoa từ A-Z và các chữ cái viết thường từ a-z. Đây là phần “mở rộng tên miền” của địa chỉ email, như “com”, “net”, “org”, v.v.
Regex không sử dụng NSRegularExpression
Có một số phương thức khác của lớp String
mà bạn có thể sử dụng để tìm kiếm và thay thế các chuỗi con, mặc dù chúng không hỗ trợ biểu thức chính quy một cách rõ ràng:
hasPrefix(_:)
: Kiểm tra xem chuỗi có bắt đầu bằng một chuỗi con cụ thể hay không.hasSuffix(_:)
: Kiểm tra xem chuỗi có kết thúc bằng một chuỗi con cụ thể hay không.range(of:options:)
: tìm kiếm một mẫu biểu thức chính quy trong một chuỗireplacingOccurrences(of:with:options:range:)
: Trả về một chuỗi mới bằng cách thay thế các khớp của một chuỗi con cụ thể bằng một chuỗi thay thế khác.
Matching Strings
Dưới đây là một ví dụ cơ bản về việc sử dụng range(of:options:)
để tìm kiếm một chuỗi con trong một chuỗi bằng Swift:
let string = "Hello, World!" let searchString = "World" if let range = string.range(of: searchString, options: .caseInsensitive) { print("Found \(searchString) at position \(string.distance(from: string.startIndex, to: range.lowerBound))") } else { print("\(searchString) not found in string") }
Trong đoạn mã trên,
range(of:options:)
tìm kiếmsearchString
trongstring
- Trả về một
Range<String.Index>
nếu tìm thấy. Nếu không tìm thấy, nó trả vềnil
. - Phương thức
distance(from:to:)
được sử dụng để tính vị trí của chuỗi con trong chuỗi.
Searching & Retrieving Matches
Ví dụ sau:
import Foundation func respond(to invitation: String) { if let range = invitation.range(of: #"\bClue(do)?™?\b"#, options: .regularExpression) { switch invitation[range] { case "Cluedo": print("I'd be delighted to play!") case "Clue": print("Did you mean Cluedo? If so, then yes!") default: fatalError("(Wait... did I mess up my regular expression?)") } } else { print("Still waiting for an invitation to play Cluedo.") } }
Đoạn mã Swift này định nghĩa một hàm respond(to:)
nhận vào một chuỗi invitation
và kiểm tra xem chuỗi đó có chứa từ “Cluedo” hoặc “Clue” không.
Đầu tiên, nó sử dụng phương thức range(of:options:)
để tìm kiếm một Regex trong invitation
. Biểu thức chính quy #"\bClue(do)?™?\b"#
khớp với từ “Clue”, có thể theo sau là “do” (được đánh dấu bởi dấu hỏi, nghĩa là phần này là tùy chọn), và có thể kết thúc bằng ký hiệu thương mại (cũng là tùy chọn).
Nếu tìm thấy khớp, nó sẽ kiểm tra chuỗi khớp đó:
- Nếu chuỗi khớp là “Cluedo”, nó sẽ in ra “I’d be delighted to play!”.
- Nếu chuỗi khớp là “Clue”, nó sẽ in ra “Did you mean Cluedo? If so, then yes!”.
- Trong trường hợp khác (không thể xảy ra với biểu thức chính quy hiện tại), nó sẽ gọi
fatalError()
.
Nếu không tìm thấy khớp, nó sẽ in ra “Still waiting for an invitation to play Cluedo.”.
Finding & Replacing Matches
Dưới đây là một ví dụ về việc sử dụng replacingOccurrences(of:with:options:)
để tìm và thay thế các khớp trong một chuỗi:
let string = "The quick brown fox jumps over the lazy dog" let pattern = "\\b\\w{4}\\b" // Matches any 4-letter word let replacement = "****" let replacedString = string.replacingOccurrences(of: pattern, with: replacement, options: .regularExpression) print(replacedString)
Trong đoạn mã trên,
replacingOccurrences(of:with:options:)
tìm kiếm tất cả các khớp củapattern
trongstring
và thay thế chúng bằngreplacement
.- Kết quả là một chuỗi mới, trong đó tất cả các từ 4 chữ cái đã được thay thế bằng “”.
Regex sử dụng NSRegularExpression
Trong các ví dụ sử dụng không cần tới NSRegularExpression. Thì chúng ta sẽ gặp được những hạn chế mà các phương thức trên không thể thực thi được. Do đó, việc xử lý biểu thức chính quy và quy về NSRegularExpression nhá.
Enumerating Matches
Dưới đây là cách bạn có thể cập nhật ví dụ trên để sử dụng enumerateMatches(in:options:range:using:)
thay vì matches(in:options:range:)
. Phương thức enumerateMatches
cho phép bạn xử lý từng khớp một cách tuần tự thay vì lấy tất cả các khớp ngay từ đầu.
import Foundation let string = "The quick brown fox jumps over the lazy dog" let pattern = "(\\b\\w{4}\\b)" // Matches any 4-letter word do { let regex = try NSRegularExpression(pattern: pattern) let range = NSRange(string.startIndex..., in: string) regex.enumerateMatches(in: string, options: [], range: range) { (match, _, _) in if let match = match { // Get the range of the first capture group let range = match.range(at: 1) if let swiftRange = Range(range, in: string) { let matchString = string[swiftRange] print("Found match: \(matchString)") } } } } catch { print("Invalid regular expression") }
Trong đoạn mã trên,
enumerateMatches(in:options:range:using:)
được gọi với một closure.- Closure này được gọi cho mỗi khớp, với khớp đó được truyền vào như một tham số.
- Điều này cho phép bạn xử lý từng khớp một cách tuần tự.
Dưới đây là cách bạn có thể cập nhật ví dụ trên để chỉ xử lý 3 lần khớp đầu tiên:
import Foundation let string = "The quick brown fox jumps over the lazy dog" let pattern = "(\\b\\w{4}\\b)" // Matches any 4-letter word do { let regex = try NSRegularExpression(pattern: pattern) let range = NSRange(string.startIndex..., in: string) var matchCount = 0 regex.enumerateMatches(in: string, options: [], range: range) { (match, _, stop) in if matchCount >= 3 { // Stop after the first 3 matches stop.pointee = true return } if let match = match { // Get the range of the first capture group let range = match.range(at: 1) if let swiftRange = Range(range, in: string) { let matchString = string[swiftRange] print("Found match: \(matchString)") matchCount += 1 } } } } catch { print("Invalid regular expression") }
Trong đoạn mã trên,
- Thêm một biến
matchCount
để theo dõi số lượng khớp đã được xử lý. - Khi
matchCount
đạt đến 3, đặtstop.pointee
thànhtrue
để dừng việc liệt kê các khớp.
Matching Multi-Line Patterns
Sử dụng NSRegularExpression
để khớp với mẫu đa dòng với nhóm bắt có tên trong Swift:
import Foundation let string = """ John Doe 123 Main St. Anytown, USA """ let pattern = """ ^(.+) (.+) (.+), (.+) """ do { let regex = try NSRegularExpression(pattern: pattern, options: [.dotMatchesLineSeparators, .anchorsMatchLines]) let range = NSRange(string.startIndex..., in: string) if let match = regex.firstMatch(in: string, options: [], range: range) { let nameRange = Range(match.range(at: 1), in: string)! let addressRange = Range(match.range(at: 2), in: string)! let cityRange = Range(match.range(at: 3), in: string)! let countryRange = Range(match.range(at: 4), in: string)! let name = string[nameRange] let address = string[addressRange] let city = string[cityRange] let country = string[countryRange] print("Name: \(name)") print("Address: \(address)") print("City: \(city)") print("Country: \(country)") } } catch { print("Invalid regular expression") }
Trong đoạn mã trên,
NSRegularExpression
được sử dụng để tạo một đối tượng biểu thức chính quy từ mẫupattern
.- Mẫu này khớp với một chuỗi có ba dòng, với mỗi dòng được bắt bởi một nhóm có tên.
- Các nhóm bắt có tên được định nghĩa bằng cú pháp
(?P<name>...)
(Swift dùng cú pháp khác)
Phương thức firstMatch(in:options:range:)
được sử dụng để tìm khớp đầu tiên trong chuỗi. Sau đó, phương thức range(withName:)
được sử dụng để lấy ra phạm vi của mỗi nhóm bắt có tên, và những phạm vi này được sử dụng để trích xuất các chuỗi tương ứng từ chuỗi gốc.
Áp dụng Raw String vào Regex
Đọc thêm về Raw String trong 10 phút.
Trong Swift 5 và phiên bản sau, bạn có thể sử dụng Raw String để viết các biểu thức chính quy một cách dễ dàng hơn. Raw String cho phép bạn viết các ký tự đặc biệt như dấu gạch chéo ngược (\
) mà không cần phải thoát chúng. Dưới đây là một ví dụ:
import Foundation let string = "The quick brown fox jumps over the lazy dog" let pattern = #"\b\w{4}\b"# // Matches any 4-letter word do { let regex = try NSRegularExpression(pattern: pattern) let matches = regex.matches(in: string, options: [], range: NSRange(string.startIndex..., in: string)) for match in matches { let range = match.range(at: 0) if let swiftRange = Range(range, in: string) { let matchString = string[swiftRange] print("Found match: \(matchString)") } } } catch { print("Invalid regular expression") }
Trong đoạn mã trên,
pattern
được định nghĩa như một Raw String bằng cách sử dụng cú pháp#"...#"
.- Điều này cho phép bạn viết biểu thức chính quy
\b\w{4}\b
mà không cần phải thoát dấu gạch chéo ngược ("(\\b\\w{4}\\b)"
)
Tạm kết
- Tìm hiểu về biểu thức chính quy và sử dụng trong Swift
- Giải thích các ký tự cú pháp cơ bản
- Các cách dùng khác cho biểu thức chính quy trong từng từng trường hợp
Với phiên bản mới nhất của Swift là 5.9, đã có nhiều thay đổi mới cho biểu thức chính quy. Hứa, một ngày đẹp trời nào đó thì sẽ viết tiếp phần 2 nhoa!
Okay! Tới đây, mình xin kết thúc bài viết giới thiệu về Biểu thức chính quy. Nếu có gì thắc mắc hay góp ý cho mình. Thì bạn có thể để lại bình luận hoặc gửi email theo trang Contact.
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Prompt Engineering trong 10 phút
- Một số ví dụ sử dụng Prompt cơ bản khi làm việc với AI
- Prompt trong 10 phút
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
You may also like:
Archives
- December 2024 (3)
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)