- Published on
๐ Swift - Regex.02
- Authors
- Name
- ์ด์ฐฝ์ค
- range(of:options:)
- NSRegularExpression
- NSRegularExpression.Options
- Swift Regex
- Regex
- Swift Regex์ ํน์ง
- Regex Builder
- ๋งค์นญ ๋ฐ์ดํฐ ์ถ์ถ (Capture)
- ์ ๋์ฝ๋ ํ์ฉ
- TryCapture
- Swift Regex ํ์ฉํ๊ธฐ
- Match
- Swift Standard Library API
Regex์ ๊ฐ๋ ์ ์ธ ๋ด์ฉ๋ค์ ์์์ผ๋, ์ด์ Swift์์๋ ์ด Regex๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์์๋ด ์๋ค.
๋ชป ๋ณด์ ๋ถ๋ค์ ์ ํฌ์คํธ๋ก!
range(of:options:)
์ด์ฐ ๋ณด๋ฉด ๊ฐ์ฅ ๊ฐํธํ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ ๋๋ค.
๊ฒ์ํด๋ดค์ ๋ ๊ฐ์ฅ ํํ๊ฒ ๋์ค๋ ๋ฐฉ๋ฒ์ด๊ธฐ๋ ํ๊ตฌ์!
str.range(of: regex, options: .regularExpression)
String
ํ์
์ ๋ฉ์๋๋ก ๊ตฌํ๋์ด ์์ด ์ฃผ์ด์ง String
ํ์
๊ฐ (์์์ ๊ฒฝ์ฐ str
) ์ ๋ํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์ค๋ ํํ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
regex
์๋ฆฌ์ ์ ๊ท์ ํํ์ ์ง์ด๋ฃ๊ณ , options
๋ก .regularExpression
์ ์ฃผ์ด ์ ๊ท์์ ์ถฉ์กฑํ๋ ๋ฒ์(Range
) ๋ฅผ ๋ฐ์์ต๋๋ค.
๊ฐ์ด ์๋๋ผ ๋ฒ์์์! (๋ฉ์๋ ์ด๋ฆ๋ถํฐ๊ฐ range
์์์? ๐
)
์ด ๋, ์ ๊ท์์ ์ถฉ์กฑํ๋ ๋ฒ์๊ฐ ์๋ค๋ฉด
nil
๊ฐ์ ๋์ ธ์ฃผ๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ณผ๋Optional
ํ์ ์ ๋๋ค.
์ด Range
๊ฒฐ๊ณผ๊ฐ์ ์๋์ String
๊ณผ ํจ๊ป string[range]
์ ํํ๋ก ์ฌ์ฉํด ์ ๊ท์์ ์ถฉ์กฑํ๋ ๋ถ๋ถ์ ์ค์ ๊ฐ์ ์ป์ด๋ผ ์ ์์ฃ .
import Foundation
let strings: [String] = ["abcde", "123", "abc123de45f"]
let pattern: String = "[0-9]*"
for string in strings {
guard let range = string.range(of: pattern, options: .regularExpression) else { continue }
print("\(string)์์ ์ซ์๋ก ์ด๋ฃจ์ด ์ง ๋ถ๋ถ์ \(string[range]) ์
๋๋ค.")
}
์ ๊น์ ์ฝ์ง ใ ใ
๊ทธ๋ฌ๋ฉด ์ ์ฝ๋์ ๊ฒฐ๊ณผ๋ ์ด๋จ๊น์?
"123"
๊ณผ "abc123de45f"์ "123"
์ด ๋์ฌ ๊ฒ ๊ฐ์ฃ ?
์๋๋๋ค ๐ โโ๏ธ
""
, "123"
, ""
์ด ๋์จ๋ต๋๋ค ๐
์ฌ์ค ์ ๋ ์ฌ๊ธฐ์ ์์ด๋ผ๋ ธ!! ํ๋ฉฐ Swift์ ํ์ผ๋ก ์๊ฐํ๋๋ฐ์..
ํด๋น ์ ๊ท์([0-9]*)์ ๊ฒฐ๊ณผ๋ ์ด๋ ์ต๋๋ค.
*
๋ ์๊ฑฐ๋ ํ๊ฐ๊ฑฐ๋ ๋ฌดํ๋์ ๊ฐ์๋ฅผ ๊ฐ์ง ๋ ๋งค์นญ๋์ฃ ?
"์์ ๋" ๋ ๋งค์นญ๋๊ธฐ ๋๋ฌธ์ "a"
๋ ""
๋ก ๋งค์นญ๋๋ ๊ฒฝ์ฐ์ธ ์
์ธ๊ฒ์ด์ฃ ..
range(of:options:)
์ ๊ฒฝ์ฐ์๋ ๊ฐ์ฅ ์ฒซ ๋งค์นญ ๋ฒ์๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ์ด ๋น ๋ฌธ์์ด์ ๋ฒ์ ๋ํ ๋งค์นญ๋ ๋ฒ์๋ก ํ๋จํด 0-0 ๋ฒ์๋ฅผ ๋ฐํํ ๊ฒ์
๋๋ค.
import Foundation
let strings: [String] = ["abcde", "123", "abc123de45f"]
let pattern: String = "[0-9]+"
for string in strings {
guard let range = string.range(of: pattern, options: .regularExpression) else { continue }
print("\(string)์์ ์ซ์๋ก ์ด๋ฃจ์ด ์ง ๋ถ๋ถ์ \(string[range]) ์
๋๋ค.")
}
์ด๋ ๊ฒ ์์ ํ๋ฉด "123"
๊ณผ "123"
์ด ๋์ต๋๋ค. ๐
range(of:options:)
๋ ํธํ์ง๋ง ๋ฌด์กฐ๊ฑด ๊ฐ์ฅ ์ฒ์์ ๋งค์นญ๋๋ ๋ฒ์๋ง์ ๋ฐํํ๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
์ด์ฉ ์ ์์ด์.. ๊ทธ๊ฒ ์ ๋ฉ์๋์ ๋ชฉ์ ์ด๊ฑฐ๋ ์.
์ ๊ท์๋ง์ ์ํ ๋ฉ์๋๊ฐ ์๋๊ณ ์ข ๋ ๋ฒ์ฉ์ ์ธ ๋ฉ์๋์ ์ ๊ท์ ๊ธฐ๋ฅ์ด ๋ฝ๋์ค๋ก ์๋ ํํ๋ผ์ ๊ทธ๋ ์ต๋๋ค.
NSRegularExpression
๊ทธ๋์ ์ฌ๊ธฐ ์ ๊ท์๋ง์ ์ํ ๋ฐฉ๋ฒ๋ ์๋ต๋๋ค!
NSRegularExpression(pattern: pattern, options: [])
NSRegularExpression
์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์
๋๋ค.
import Foundation
let strings: [String] = ["abcde", "123", "abc123cd45e"]
let pattern: String = "[0-9]*"
var results: [[String]] = []
for string in strings {
do {
let regex = try NSRegularExpression(pattern: pattern)
let result = regex.matches(in: string, range: NSRange(location: .zero, length: string.count))
let resultStrings = result.map {
let range = Range($0.range, in: string)!
return String(string[range])
}.filter {
!$0.isEmpty
}
results.append(resultStrings)
} catch {
print(error.localizedDescription)
}
}
results.forEach { print($0) }
์ฐ์ NSRegularExpression
์ ์์ฑ์๋ throwableํ๊ธฐ ๋๋ฌธ์ try-catch
๋ฌธ์ ์ฌ์ฉํด์ฃผ์
์ผ ํด์. (9๋ฒ ๋ผ์ธ)
matches
๋ฉ์๋๋ฅผ ์ฌ์ฉํด string
์์ ์ ๊ท์์ ์ถฉ์กฑํ๋ ๋ชจ๋ ๋ถ๋ถ๋ค์ ๋ํ NSTextCheckingResult
ํด๋์ค๋ฅผ ๋ฐฐ์ด๋ก ๋ฐ์์ต๋๋ค. (10๋ฒ ๋ผ์ธ)
์ด NSTextCheckingResult
์ range
ํ๋กํผํฐ๋ฅผ ์ด์ฉํ๋ฉด ์์ range(of:options:)
๋ฉ์๋์ ๋์ผํ ๋ฐฉ๋ฒ์ผ๋ก ๋งค์นญ ๋ด์ฉ์ ์ป์ด๋ผ ์ ์์ต๋๋ค. (์์์ฒ๋ผ map
์ ํ์ฉํ๋ฉด ํธํ๊ฒ ์ฃ ?)
NSRegularExpression.Options
ํ ๊ฐ์ง ์ ๋ณธ๊ฒ ์์ต๋๋ค.
๋ฐ๋ก options:
ํ๋ผ๋ฏธํฐ์ ๋ค์ด๊ฐ ๊ฐ์ธ๋ฐ์..
init(
pattern: String,
options: NSRegularExpression.Options = []
) throws
๊ธฐ๋ณธ๊ฐ์ผ๋ก๋ ๋น ๋ฐฐ์ด์ด ๋ค์ด๊ฐ ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์ ์ด 7๊ฐ์ ๊ฐ์ ๋ฃ์ด์ค ์ ์๋ต๋๋ค.
์์งํ ๋ง์ด ์ฌ์ฉํ ๊น? ์ถ์ด์ ์์ธํ ๋ค๋ฃจ์ง๋ ์๊ฒ ์ต๋๋ค.
๊ทธ๋๋ ์ด์ง ๋ง๋ณด๊ธฐ
caseInsensitive
์ง๊ด์ ์ธ ์ด๋ฆ์ ๊ฐ์ง๊ณ ์๋ ์ต์ ์ ๋๋ค.
๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ์ง ์๊ณ ๋งค์นญ์ ํ๊ฒ ๋ค๋ ์ต์ ์ ๋๋ค.
์๋ฅผ ๋ค์ด ABCD
๋ฅผ ์ ๊ท์์ ๋ฃ์ด๋ abcd
๊น์ง ๋งค์นญ์ด ๋๊ฒ ๋ง๋๋ ๊ฒ์ด์ฃ !
allowCommentsAndWhitespace
whitespace์ ๋งจ ์์ #๋ฅผ ๋ฌด์ํ๋ค! ๋ผ๋ ์ต์ ์ ๋๋ค.
๋ฌธ์ ๋ whitespace์ ๊ฒฝ์ฐ์๋ a b cd
๋ฅผ abcd
๋ก ์ ์ฉํ๋ค๋ ๊ฒ ๊น์ง ์ดํด ๋ฒ์ฃผ ์ด๋ด์ธ๋ฐ..
์ฐธ๊ณ ๋ฅผ ํ Zedd๋์ ํฌ์คํธ์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ ๋ #๋ฅผ ๋ฌด์ํ๋ค๋ ๊ฑด ์ด๋ค ์๋ฏธ์ธ๊ฑด์ง ์ฐพ์ ์ ์์์ต๋๋ค...
ignoreMetacharacters
pattern
์ ๊ธฐํธ๋ค์ ์ ๋ถ ๋จ์ํ ๋ฌธ์์ด๋ก ์ฒ๋ฆฌํ๋ ์ต์
์
๋๋ค.
dotMatchesLineSeparators
pattern
์ .
์ ์ค๋ฐ๊ฟ์ผ๋ก๋ ๋์์์ผ ๋งค์นญํ๋ ์ต์
์
๋๋ค.
์๋ฅผ ๋ค์ด "a.b" ํจํด์ "a\nb"๋ ๋งค์นญ๋๋ ๊ฒ์ด์ฃ .
anchorsMatchLines
^
์ $
๋ฅผ ๊ฐ ์ค๋ง๋ค ์ ์ฉ์ํค๋ ์ต์
์
๋๋ค.
"""
abcdefg
abc
defg
"""
๊ฐ ์์ ๋ "^abc"
๊ฐ ํจํด์ผ๋ก ์ฃผ์ด์ง๋ค๋ฉด ์ฒซ ๋ฒ์งธ ์ค๊ณผ ๋ ๋ฒ์งธ ์ค์ด ๋งค์นญ๋๋ ์ต์
์
๋๋ค.
์ฌ๊ธฐ๊น์ง๊ฐ ๊ธฐ์กด์ ์ฌ์ฉํ๋ ๋ฐฉ์๋ค์ด๊ตฌ์..! ์ง์ง๋ ์ง๊ธ๋ถํฐ์ ๋๋ค.
Swift Regex
์ฌ์ค ์ด ํฌ์คํธ๋ ์ด์ ๋ด์ฉ๋ค์ด ์๋๋ผ ์ง๊ธ๋ถํฐ ๋์ฌ ๋ด์ฉ๋ค์ ์ ๋ฆฌํ๊ธฐ ์ํ ํฌ์คํธ์์ด์! ๐
์ ๋ด์ฉ๋ค.. ์กฐ๊ธ ์ง๊ด์ ์ด์ง ๋ชปํ๊ณ Swiftyํ์ง๋ ์์ฃ ?
Obj-C ์์ ๋ถํฐ ์ฐ๋ ์ฝ๋๋ค์ด๋ผ ๊ทธ๋ ์ต๋๋ค. (NS
๊ฐ ๋ถ์๊ฑฐ์์๋ถํฐ ์์
จ์ ๊ฒ ๊ฐ์์ ๐
)
WWDC22์์๋ ์ ๊ท์์ ์ข ๋ Swiftyํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ์๋ก์ด API๊ฐ ๋ฑ์ฅํ์ต๋๋ค.
๊ทธ๋์ ์ด์ ๋ถํฐ ๊ทธ ๋ด์ฉ์ ์ดํด๋ณผ๊ฒ์!
Regex
์ฐ๋ฆฌ๋ฅผ ๊ตฌ์ํ ๊ทธ ๊ตฌ์กฐ์ฒด๋ถํฐ ๋ฑ์ฅํฉ๋๋ค.
struct Regex<Output> { ... }
Regex
๊ตฌ์กฐ์ฒด๋ Output
์ ๋๋ฆญ ํ์
์ ๊ฐ์ง๋๋ค.
์ด Output
์ ๋ง ๊ทธ๋๋ก ๊ฒฐ๊ณผ๋ฅผ ์ํด ์ฌ์ฉ๋๋ ํ์
์ด๊ตฌ์.
Regex
๋ ํน์ดํ๊ฒ๋ /
๋ก ๋ฌธ์๋ค์ ๊ฐ์ธ ์ด๊ธฐํํ ์ ์์ต๋๋ค.
์ปดํ์ผ๋ฌ ๋ํ ์ด ๋ฌธ๋ฒ์ ์๊ณ ์๊ธฐ ๋๋ฌธ์(!) ์ปดํ์ผ ์๋ฌ๋ฅผ ํตํด ๋ฌธ๋ฒ์ ์ค๋ฅ๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
let digits = /\d+/
// digits: Regex<Substring>
์ฌ์ฉ์์ ์ ๋ ฅ์ ๋ฐ์ ํจํด์ ์ค์ ํ๋ ๋ฑ์ ๋ฐํ์๋์ ํจํด์ด ๊ฒฐ์ ๋์ผ ํ๋ค๋ฉด
let runtimeString = #"\d+"#
let digits = try Regex(runtimeString)
// digits: Regex<AnyRegexOutput>
์ด๋ ๊ฒ ๋ฌธ์์ด์ ์ ๊ณตํ๋ ํํ์ ์์ฑ์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
try
๋ฌธ์์ ์ ์ถํ ์ ์๋ค์ถ์ด, ์ฌ๋ฐ๋ฅด์ง ์์ ๋ฌธ๋ฒ์ด ๋ค์ด์จ๋ค๋ฉด ๋ฐํ์ ์๋ฌ๋ฅผ ๋ฐ์์ํค๊ธฐ๋ ํ๋ต๋๋ค.
๋ค๋ง ์ด ๊ฒฝ์ฐ์๋ ๋ฐํ์ ์ ๊น์ง๋ Output
ํ์
์ ์ถ์ ํ ์ ์๊ธฐ ๋๋ฌธ์ AnyRegexOutput
์ด๋ผ๋ ์ผ์ข
์ Any
ํ์
์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
๋ฐ๋ก ๋ค์์ ๋ ์ดํด๋ณด๊ฒ ์ง๋ง,
let digits = OneOrMore(.digit)
// digits: Regex<Substring>
์ด๋ ๊ฒ Regex Builder๋ผ๋ API๋ฅผ ํตํด ์์ฑ๋๋ ์ธ์คํด์ค ๋ํ Regex
๊ตฌ์กฐ์ฒด์
๋๋ค.
Swift Regex์ ํน์ง
์ด์ฏค์์ ํน์ง์ด๋ผ ์ฐ๊ณ ์ฅ์ ์ด๋ผ๊ณ ์ฝ๋ ์ ํ์ด ์๊ฐํ๋ Swift Regex์ ํน์ง์ ์์๋ด ์๋ค.
- Regex Builder๋ฅผ ํตํด ์ฝ๋๋ฅผ ์์ฑํ๋ฏ์ด ๊ตฌ์กฐํ๋๊ณ ์ ๋๋ ๋ฐฉ๋ฒ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค.
- ์ค์ Parser๋ฅผ ์ ๊ท์์ ๊ตฌ์ฑ์์๋ก์ ๊ฐ์ ธ๋ค ์ธ ์ ์๋ค.
- Unicode๋ฅผ ์ง์ํด ์ด๋ค ์ธ์ด๋์ง ๋์ํ ์ ์๋ค.
- ์์ธก ๊ฐ๋ฅํ ์คํ ๋ฐฉ์์ ์ ๊ณตํด ์ด๋ค ๋ถ๋ถ์ด ๋ฌธ์ ์ธ์ง ํ์ ํ๊ธฐ ์ฝ๋ค.
Regex Builder
์ ๊ทธ๋์ ์ด๋ ๊ฒ๊น์ง ์ข๋ค๊ณ ๋๋ฆฌ๋ถ๋ฅด์ค์ธ Regex Builder๋ ๋ฌด์์ผ๊น์?
Regex Builder๋ ์ ๊ท์์ "์ ์ธํ" ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ ์ ์๋๋ก ํ๋ API์ ๋๋ค.
import RegexBuilder
RegexBuilder
๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์ฐ์ import
๋ฅผ ํด์ค์ผํ๋ค๋ ์ ์์ง ๋ง์๊ตฌ์!
"""
CREDIT 03/02/2022 Payroll from employer $200.23
CREDIT 03/03/2022 Suspect A $2,000,000.00
DEBIT 03/03/2022 Ted's Pet Rock Sanctuary $2,000,000.00
DEBIT 03/05/2022 Doug's Dugout Dogs $33.27
"""
์ด์ ์ง์ง๋ก ์ฌ์ฉ๋ฒ์ ์์๋ด ์๋ค.
์ด๋ฐ ๋ฐ์ดํฐ๊ฐ ์๋ค๊ณ ํด๋ณผ๊ฒ์.
์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ ํ๋์ ๋ฐ๋ผ ๋ฐ์ดํฐํ ํ๊ธฐ ์ํด์ ์ ๊ท์์ ์ฌ์ฉํด๋ด ์๋ค.
let fieldSeparator = /\s{2,}|\t/
let transactionMatcher = Regex {
/CREDIT|DEBIT/
fieldSeparator
One(.date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt))
fieldSeparator
OneOrMore {
NegativeLookahead { fieldSeparator }
CharacterClass.any
}
fieldSeparator
One(.localizedCurrency(code: "USD").locale(Locale(identifier: "en_US")))
}
์ด๋ผ.. ๋ญ๊ฐ ์ง๋๊ฐ๋์? ๐ต
์ ์ธํ์ด๋ผ ํ๋์ฉ ์ดํด๋ณด๋ฉด ์ฌ์ฐ๋๊น ๊ฒ ๋จน์ง ๋ง๊ณ ํ ๋ฒ ๋ค์ด๊ฐ๋ด ์๋ค. (์ ํํ ํ๋ ์๊ธฐ ๋ง์์. ๐)
์ผ๋จ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋ Regex
์์ฑ์์ ํด๋ก์ ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ๊ฐ ์ปดํฌ๋ํธ๋ค์ ์ ์ธํ์ผ๋ก ์ ๊ณตํ๊ณ ์๋ค์.
๊ฒ์ฌํ String
์ ์ผ์ชฝ๋ถํฐ ์ค๋ฅธ์ชฝ ์์๋๋ก ์ปดํฌ๋ํธํ ์์ผ์ ๊ฒ์ฌ๋ฅผ ํ๋ค๋.. ์์ฃผ ์ง๊ด์ ์ด๊ตฐ์...
์ด์ ์ปดํฌ๋ํธ๋ณ๋ก ํ๋์ฉ ์ดํด๋ด ์๋ค.
๋จผ์ fieldSeparator
๋ space ๊ธฐ์ค 2์นธ ์ด์์ whitespace๋ฅผ ๋งค์นญํ๋ ์ผ๋ฐ์ ์ธ ํํ์ ์ ๊ท์์ผ๋ก ์ ์๋์ด ์๋ค์.
๊ฐ๊ฐ์ ํ๋๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ํด ์ฌ์ฉํด์ฃผ๊ณ ์๋ ๊ฒ ๊ฐ์ฃ ?
CREDIT
๊ณผ DEBIT
๋ ์ฝ์ต๋๋ค.
๋ ๊ฐ์ง ๊ฒฝ์ฐ์ ์ ๋ฐ์ ์๊ธฐ ๋๋ฌธ์ CREDIT|DEBIT
์ด๋ฉด ์ถฉ๋ถํ์ฃ .
๋ ์ง๋ฅผ ํ์ฑํ ๋ Regex Builder
์ ํน์ง์ด ์ ๋ค์ด๋ฉ๋๋ค.
"์ค์ Parser๋ฅผ ์ ๊ท์์ ๊ตฌ์ฑ์์๋ก์ ๊ฐ์ ธ๋ค ์ธ ์ ์๋ค."๋ผ๋ ํน์ง์ด ์์์ฃ ?
String
๊ฐ์ One
์ด๋ผ๋ Regex
๊ตฌ์กฐ์ฒด์ .date
๋ผ๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ๋ ์ง ํ์์ผ๋ก ๋ถ๋ฌ์ฌ ์ ์๋ค๋ ๋ป ์
๋๋ค.
.numeric
์ธ์ ๋ค๋ฅธ ํ์์ผ๋ก ํ์ฑ๋ ๊ฐ๋ฅํ๊ณ , locale
๊ณผ timeZone
๋ ์ค์ ์ด ๊ฐ๋ฅํ ์ ์ ๊ตณ์ด ์ค๋ช
ํ์ง ์์๋ ์ ์ ์์ฃ ! ๐
์ ์ผ ๋ณต์กํด๋ณด์ด๋ ์ธ ๋ฒ์งธ ํ๋์ ๋๋ค.
OneOrMore { CharacterClass.any }
์ด๋ฏธ ์ฃผ์ด์ง ์ ๋ต ๋์ ์ด๋ ๊ฒ ์ฌ์ฉํ๋ฉด ๊ฒฐ๊ณผ๋ ์ด๋ป๊ฒ ๋์ฌ๊น์?
๋ต์ "๋์ผํ๋ค." ์ ๋๋ค.
์ด๋ค ์บ๋ฆญํฐ๋์ง ํ๋ ํน์ ๊ทธ ์ด์์ ์ฐพ์๋ด๊ณ , ๋ฐ๋ก ๋ค์์ fieldSeparator
๊ฐ ๋ค์ ์ค๊ธฐ ๋๋ฌธ์ whitespace๋ค ์ ๊น์ง์ ๊ฐ๋ง ์ ์์ ์ผ๋ก ๋งค์นญ๋ฉ๋๋ค.
๊ทธ๋ฐ๋ฐ ์ ๋ต์์ ํจ์ฌ ๋ณต์กํด๋ณด์ด๋
OneOrMore {
NegativeLookahead { fieldSeparator }
CharacterClass.any
}
์ ์ฌ์ฉํ ์ด์ ๋ ์ต์ ํ๋ฅผ ์ํด์์ ๋๋ค.
Regex Builder
๋ fieldSeparator
๋ฅผ ๋ง๋๊ธฐ ์ ๊น์ง๋ OneOrMore { CharacterClass.any }
์ ๋งค์นญ๋๋ String
์ ๋๊น์ง๋ฅผ ๋งค์นญ ๋ฒ์๋ผ๊ณ ์๊ฐํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ์ดํ์ fieldSeparator
๋ฅผ ๋ง๋ ํ์ ๋๊น์ง ์๊ฐํ ๋งค์นญ ๋ฒ์์์ ํ๋์ฉ ๋ฒ์๋ฅผ ์ค์ฌ๋๊ฐ๋ฉฐ ์๋ก์ด ์กฐ๊ฑด์ ๋งค์นญ๋๋ ์์๋ฅผ ์ฐพ์๋๊ฐ๋ ๊ฒ์ด์ฃ .
์ ๋ถ ์ก์๋๊ณ ํ๋์ฉ ์ค์ฌ๊ฐ๋ฉฐ ๊ฒ์ฌํ๋ ๊ฒ ๋ณด๋ค๋ ์ ์ด์ ์ฒ์๋ถํฐ ์ ์ก์๋๋ ๊ฒ์ด ๋ ํจ์จ์ ์ด๊ฒ ์ฃ ?
์ ๋ต์ ์์๋ NegativeLookahead
๋ฅผ ์ฌ์ฉํด ๋ค์ ๊ฐ์ด fieldSeparator
์ธ์ง๋ฅผ ๊ฒ์ฌํ๋ฉฐ ๋งค์นญ๋๋ ์์๋ผ๋ฉด ์ค๋จํ๋ ๋ฐฉ์์ผ๋ก ์งํํ ๋ชจ์ต์
๋๋ค.
NegativeLookahead๊ฐ ๋ญ๊ฐ์?
Lookahead๋ Regex์ ๊ณ ๊ธ ๋ฌธ๋ฒ ์ค ํ๋๋ก, 4๊ฐ์ ์ข ๋ฅ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
์.. ํท๊ฐ๋ฆฌ์ฃ ? ์ ๋์ ๐
์ ์ด๋ฏธ์ง์ ์ถ์ฒ์ธ ์๋ฐ๋ ธํ๋์ ํฌ์คํธ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
์ ๋ ํท๊ฐ๋ฆด ๋๋ง๋ค ์ฐพ์๊ฐ๋ ํฌ์คํธ๋๋๋ค.
ํ ๊ฐ์ง๋ง ๋ ํ์ธํ๊ณ ๋์ด๊ฐ๊น์?
OneOrMore
๊ณผ ๊ฐ์ Repetition์ ๊ดํ ์ด์ผ๊ธฐ์ ๋๋ค.
Swift Regex๋ OneOrMore
, ZeroOrMore
, Optioinally
, Repeat
๊ณผ ๊ฐ์ ๋ฐ๋ณต์ ์ผ๋ก ์ํ๋๋ ํํ์๋ค์ด ์์ต๋๋ค.
์ด ๊ธฐ๋ฅ๋ค์ ๊ธฐ๋ณธ์ ์ผ๋ก .eager
ํ๊ฒ ๋์ํฉ๋๋ค.
OneOrMore(.any, .eager)
์ด๋ ๊ฒ์.
์ ์๋ฆฌ์ ๋ค์ด๊ฐ ์ ์๋ ์ต์ ๋ค์ ์ดํด๋ด ์๋ค. (3๊ฐ ๋ฐ์ ์์ด์!)
.eager
์ด๋ฆ๋ต๊ฒ ๊ฐ๋ฅํ ๋ชจ๋ ๋งค์นญ์ ๊ฐ์ ธ๊ฐ๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ค์ ์ค๋ Optionally
์ ๊ฐ์ ๊ฒฝ์ฐ๋ฅผ ๋ฌด์ํ๋ ๊ฒฝ์ฐ๋ ์๊ธฐ์ฃ .
.reluctant
.eager
๊ณผ๋ ๋ค๋ฅด๊ฒ ๊ฐ๋ฅํ ์ต์ํ์ ๋งค์นญ์ ๊ฐ์ ธ๊ฐ๋๋ค.
์ด ์ต์
์ ์ฌ์ฉํ๋ฉด ์์ Optionally
๋ฅผ ๋ฌด์ํ๋ ๊ฒฝ์ฐ๋ ์๊ฒ ์ฃ ?
.possessive
๊ฐ๋ฅํ ๋ชจ๋ ๋งคํ์ ๊ฐ์ ธ๊ฐ๊ณ , ๋ฐฑํธ๋ํน์กฐ์ฐจ ํ์ง ์์ต๋๋ค.
์ด ์ต์ ๋ค์ ๊ฐ๊ฐ์ Repetition๋ค์ ์ ์ฉํด์ค ์๋ ์์ง๋ง,
Regex {
OneOrMore(.digit)
"."
OneOrMore(.digit)
}
.repetitionBehavior(.reluctant)
์ด๋ ๊ฒ ๋งจ ๋ค์ .repetitionBehavior
๋ก Regex
์ ๊ธฐ๋ณธ ์ต์
์ ๋ณ๊ฒฝํด์ค์ผ๋ก์จ ์ ์ฉํด์ค ์๋ ์์ต๋๋ค.
๋ง์ง๋ง ๊ธ์ก ๋ถ๋ถ์ Date
๋ถ๋ถ๊ณผ ๋น์ทํ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํด์ฃผ์๋ฉด ๋ฉ๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก Foundation
์ Parser์ธ .localizedCurrency
๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค์.
๋งค์นญ ๋ฐ์ดํฐ ์ถ์ถ (Capture)
์ ๊ทธ๋ฐ๋ฐ ์ฐ๋ฆฌ๋ ์ด ๋ฐ์ดํฐ๋ค์ ๋จ์ํ ์ธ์ํ๋ ๊ฒ์ด ์๋๋ผ ๋งค์นญ๋ ๊ฐ๋ค์ ์ถ์ถํ๊ณ ์ถ์ต๋๋ค.
ํด๋น ๊ฐ๋ค์ ์ฌ์ฉํด์ผ ํ์์์?
์ผ๋ฐ์ ์ธ Regex์๋ ์ด๋ฐ ๊ธฐ๋ฅ์ ์์ต๋๋ค.
Named Capture Group์ด๋ผ๋ ์ด๋ฆ์ ๊ธฐ๋ฅ์ธ๋ฐ์, (?<identifier>\d+)
๋๋ต ์ด๋ฐ ํํ์
๋๋ค.
\d+
์ ์กฐ๊ฑด์ ๋งค์นญ๋๋ ๊ทธ๋ฃน์ ๊ฐ์ identifier
๋ผ๋ ์ด๋ฆ์ผ๋ก ์ ๊ทผํด ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์
๋๋ค.
Regex Builder
๋ ๋ฌผ๋ก ์ด๋ฐ ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค.
Capture
๋ฅผ ํตํด์์!
let fieldSeparator = /\s{2,}|\t/
let transactionMatcher = Regex {
Capture { /CREDIT|DEBIT/ }
fieldSeparator
Capture { One(.date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt)) }
fieldSeparator
Capture {
OneOrMore {
NegativeLookahead { fieldSeparator }
CharacterClass.any
}
}
fieldSeparator
Capture { One(.localizedCurrency(code: "USD", locale: Locale(identifier: "en_US"))) }
}
// transactionMatcher: Regex<(Substring, Substring, Date, Substring, Decimal)>
๊ทธ๋ฐ๋ฐ ์ด๋ ํ์ฑ๋๋ ๊ฐ๋ค์ Date
์ Decimal
๋ ํฌํจ๋์ด ์์ฃ ?
Foundation
Parser๋ฅผ ํ์ฉํ๋ Regex Builder
์ ๊ฐ์ ์ด๋ผ๊ณ ํ ์ ์๊ฒ ์ต๋๋ค.
guard let match = transaction.wholeMatch(of: transactionMatcher) else {
fatalError()
}
print(match.output.2, match.output.4)
์ด๋ ๊ฒ ์ถ์ถํด๋ธ ๊ฐ๋ค์ output.2
์ ๊ฐ์ด ํํ์ ํํ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
/.../
ํํ๋ฅผ ์ฌ์ฉํ ๋๋ ๊ดํธ()
๋ฅผ ํตํด์ Capture๋ฅผ ํ ์ ์์ต๋๋ค.
let regex = Regex {
"a"
/d(e)f/
}
์ฌ๊ธฐ์๋ ํ๋๋ง ๋ ์์๋ด ์๋ค! Transforming Capture๋ผ๋ ๊ธฐ๋ฅ์ ๋๋ค.
Transforming Capture๋ Captureํ ๊ฐ์ ๋ค๋ฅธ ํน์ ํ ๋ฐ์ดํฐ ํ์ ์ผ๋ก ๋ณํํด์ Captureํ๊ณ ์ถ์ ๋ ์ฌ์ฉํฉ๋๋ค.
Regex {
Capture {
OneOrMore(.digit)
} transform; {
Int($0)
}
} // Regex<(Substring, Int?)>
Regex {
TryCapture {
ChoiceOf {
"started"
"passed"
"failed"
}
} transform: {
TestStatus(rawValue: String($0))
}
} // Regex<(Substring, TestStatus)>
์ ๋์ฝ๋ ํ์ฉ
๋ค์ ์ธ์ ์ผ๋ก ๋์๊ฐ๋ด ์๋ค.
๋ง์์ ๋ค ์ก๊ณ ๋ค์ ์ง์ค์ผ๋ก ํ๋ ค๊ณ ํ๋ ์ฐฐ๋.. ์ ํ์ด ๊ฐ์๊ธฐ ๊ธ๋ฐ์ง์ ํฉ๋๋ค...!
private let ledger = """
KIND DATE INSTITUTION AMOUNT
----------------------------------------------------------------
CREDIT 03/01/2022 Payroll from employer $200.23
CREDIT 03/03/2022 Suspect A $2,000,000.00
DEBIT 03/03/2022 Ted's Pet Rock Sanctuary $2,000,000.00
DEBIT 03/05/2022 Doug's Dugout Dogs $33.27
DEBIT 06/03/2022 Oxford Comma Supply Ltd. ยฃ57.33
"""
์ฌ๊ธฐ์ DATE ๋ถ๋ถ์ ๊ฐ์ด ์/์ผ/๋ ์ธ์ง ์ผ/์/๋ ์ธ์ง ์ ๋งค๋ชจํธํ๋ค!
ํ์ง๋ง AMOUNT์ ๋ฌ๋ฌ ์ฌ์ธ๊ณผ ์ ๋ก ์ฌ์ธ์ ํตํด ์ ์ถ๊ฐ ๊ฐ๋ฅํ๋ ์ด๊ฑธ ํ์ฉํด๋ณด์!
๋ผ๊ณ ํ๋๋ ๊ฐ์๊ธฐ ์ด๋ฐ๊ฑธ ๋ค์ด๋๋๋ค.
let regex = #/
(?<date> \d{2} / \d{2} / \d{4})
(?<middle> \P{currencySymbol}+)
(?<currency> \p{currencySymbol})
/#
// Regex<(Substring, date: Substring, middle: Substring, currency: Substring)>
...? ๐
#/ ... /#
๋ฅผ ์ฌ์ฉํ๋ฉด whitespace๋ฅผ ๋ฌด์ํด์ ์ผ๋ฐ ์ฝ๋์ฒ๋ผ ๊ฐ๋
์ฑ ์๋ ์ฝ๋ฉ์ ํ ์ ์๋ค!
๊น์ง๋ ๋ญ ์๊ฒ ์ด์.
\P{currencySymbol}
์ด ๋์ฒด ๋ญ๋ฐ ํ๋ฉด์ ์ด๊ฒ์ ๊ฒ ์ฐพ์๋ณด๋ ์ค...
์.. Regex์ ์๋ ์๋ ํํ์ด์์ต๋๋ค. (์ถ์ฒ๋ ์ ํ ๊ณต์ ๋ฌธ์์ ๋๋ค.)
Unicode์์ ์ ์ํ ํ๋กํผํฐ์ ์ํด์๋์ง ๊ฒ์ฌํ๋ ํํ๋ค์ด๋ผ๊ณ ํ๋ค์.
\p{}
๊ฐ ํ๋กํผํฐ์ ์ํด์๋์ง์ด๊ณ \P{}
๋ ํ๋กํผํฐ์ ์ํด์์ง ์์์ง๋ฅผ ๊ฒ์ฌํ๋ ํํ์ด๋ผ๊ณ ํด์.
์ง์ ๋ ์ ๋์ฝ๋ ํ๋กํผํฐ๋ค์ ์ฌ๊ธฐ์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
๊ทผ๋ฐ์.. ๋ currencySymbol
์ ๊ทธ๋ฃน ์ด๋ฆ์ ์๋๋ผ๊ณ ์..?
๋์ ๋น์ค๋ฌด๋ฆฌํ ๊ฑด ์์์ต๋๋ค.
\\p\{Sc\}
or\\p\{Currency_Symbol\}
: any currency sign.
์ ํ์ด ๋ ๋๋ฆ๋๋ก ๋ํ์ ํ ๊ฑด์ง ๋ญ์ง... ์ ์๊ฐ ์๊ตฐ์.
๋ญ๊ฐ ์์๋ด๋ฉด ๋ด์ฉ์ ์ถ๊ฐํด๋๋๋ก ํ๊ฒ ์ต๋๋ค.
ํํ ๐ฒ Swift์ ์ ์๋์ด ์๋๊ฒ ๋ง๋๊ตฐ์!
Unicode ๋ฌธ์๋ฅผ ๋ณด์๋ฉด ๋ฏธ๋ฆฌ ๋ถ๋ฅ๋ case๋ค์ด enum
์ผ๋ก ์ ์๋์ด ์๊ณ ..
๊ทธ ์ค GeneralCategory
์ currencySymbol
์ด ์ ์๋์ด ์์ต๋๋ค.
์ฌ์ค WWDC22 ์์์์๋ "Unicode Property" ๋ฅผ ์ฌ์ฉํ๋ค๊ณ ์ธ๊ธํ๋๋ฐ์, ์ ๊ฐ ๋ชจ๋ฅด๋ ๊ฐ๋ ์ด์๊ธฐ ๋๋ฌธ์ ๋ ์ฉํ๋๊ฒ ์๋๊น ์ถ์ต๋๋ค ๐
์ญ์ ์ค๋๋ ๋จนํ๋ ์๋๋งํผ ๋ณด์ธ๋ค...
๋ค์ ๋์๊ฐ์...
์ด์ ์ฐ๋ฆฌ๋ (?<currency> \p{currencySymbol})
์ด๊ฑธ ํตํด์ ํตํ์ ๊ธฐํธ๋ฅผ ๋ฐ์์ฌ ์ ์์ฃ ?
func pickStrategy(_ currency: Substring) -> Date.ParseStrategy {
switch currency {
case "$": return .date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt)
case "ยฃ": return .date(.numeric, locale: Locale(identifier: "en_GB"), timeZone: .gmt)
default: fatalError("We found another one!")
}
}
๊ทธ๊ฑธ ์ด๋ฐ ์์ผ๋ก ํ์ฉํด ๋ ์ง๋ฅผ ํ์ฑํ๋ ๋ฐฉ๋ฒ์ ์ฐจ์ด๋ฅผ ๋ง๋ค์ด์ฃผ๋ ค๊ณ ํ๋๋ด ๋๋ค.
ledger.replace(regex) { match -> String in
let date = try! Date(String(match.date), strategy: pickStrategy(match.currency))
// ISO 8601, it's the only way to be sure
let newDate = date.formatted(.iso8601.year().month().day())
return newDate + match.middle + match.currency
}
์ฃผ์ด์ง ๋ฌธ์์ด์์ regex
๋ฅผ ์ฌ์ฉํด ์ ๋งค๋ชจํธํ๋ ๋ ์ง ๋ถ๋ถ์ ํตํ์ ๋ฐ๋ผ ๋ค๋ฅธ ํ์ฑ ๋ฐฉ์์ ์จ์
ํ์คํ ํํ์ newDate
๋ก ๋ณํํด์ ์ ์ฅํด์ฃผ๋ ํจ์์
๋๋ค.
์์ ํํ์์ ์๋ ํํ๋ก์!
์ฌ๊ธฐ๊น์ง ๋ชจ๋ ๊ฒ ์๋ฒฝํด ๋ณด์ด์ง๋ง, ํ ๊ฐ์ง ๊ณ ๋ คํ ์ ์ด ํ๋ ๋ ์์ต๋๋ค.
TryCapture
์ง๊ธ๊น์ง ์ ํฌ๊ฐ ์ฌ์ฉํ ์ ๊ท์์ ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ์ ์๋ฆฌ์ ์์ ๋๋ง! ๋์ํ๊ฑฐ๋ ์.
์ด๋ค ํ๋์ ์๋ฆฌ์ ์กฐ๊ฑด์ ๋ง๋ ๋ฐ์ดํฐ๊ฐ ์์ ๋๋ ๋งค์นญ์ด ์คํจํด์ ํด๋น ๋ ์ฝ๋ ์์ฒด๊ฐ ์ธ์๋์ง ๋ชปํ๊ฒ ์ฃ ?
Swift๋ ์ ๊ท์์ Optional
์ ๋์
ํ TryCapture
์ด๋ผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
// CREDIT <proprietary> <redacted> 200.23 A1B34EFF ...
let fieldSeparator = /\s{2,}|\t/
let field = OneOrMore {
NegativeLookahead { fieldSeparator }
CharacterClass.any
}
let transactionMatcher = Regex {
Capture { /CREDIT|DEBIT/ }
fieldSeparator
TryCapture(field) { timestamp ~= $0 ? $0 : nil }
fieldSeparator
TryCapture(field) { details ~= $0 ? $0 : nil }
fieldSeparator
// ...
}
๋ด์ฉ์ ์๊ด์์ด ํ๋๋ณ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฆฌํ๊ธฐ ์ํด NegativeLookahead
๋ฅผ ๋ค์ ํ ๋ฒ ํ์ฉํด field
๋ฅผ ์ ์ํด์ฃผ๊ณ ์..
TryCapture(field)
๋ฅผ ์ฌ์ฉํด field
๋ฒ์์ ๊ฐ์ด timestamp
๋ details
์ ๋ฒ์์ ์๋ค๋ฉด ํด๋น ๊ฐ์ "Capture"ํ๊ณ , ๋ฒ์์์ ๋ฒ์ด๋ ๊ฐ์ด๋ผ๋ฉด nil
์ "Capture"ํฉ๋๋ค.
์ด์ ์ง์ง ๋์ด๊ฒ ์ฃ ? ๐
ํ๋ ๋ ๋จ์์ต๋๋ค ๐
์ง๊ธ๊น์ง ์ฌ์ฉํ๋ fieldSeparator
์์ ๋ฌธ์ ๊ฐ ํ๋ ๋ฐ์ํ๋๋ฐ์,
let fieldSeparator = /\s{2,}|\t/
2๊ฐ ์ด์์ space ํน์ ํญ์ ๋งค์นญ์ํค๋ ์ ๊ท์์ด์์ฃ ?
์ด ์ค์์ 2๊ฐ ์ด์์ space๊ฐ ๋ฌธ์ ๊ฐ ๋๋ค๊ณ ํฉ๋๋ค.
์ดํ์ ์ค๋ ํจํด์ด ์คํจํ๋ฉด, ๋งค์นญ๋์๋ space์ ๊ฐ์๋ฅผ 1๊ฐ์ฉ ์ค์ฌ๊ฐ๋ฉฐ ์ต์ ๊ฐ์์ธ 2๊ฐ๊ฐ ๋ ๋๊น์ง ๊ณ์ํด์ ๋ค์ ์๋ํ๋ค๊ณ ํ๋ค์.
์ ํ์ด ๋ง๋ ์ ๋๋ฉ์ด์ ์ ์ฐธ๊ณ ํ๋ฉด ์๋ง ์ดํด๊ฐ ๋์ค ๊ฑฐ์์!
์ด ํ์์ Regex์ Global Backtracking์ด๋ผ๋ ํน์ง ๋๋ฌธ์ ์ผ์ด๋ฉ๋๋ค.
"๋๋ฌธ์"๋ผ๊ธฐ์ ์ด ํน์ง ๋๋ถ์ ์ ๊ทํํ์์ด ๊ฐ๋ ฅํ๊ฑฐ๊ธด ํฉ๋๋ค. ๋ค๋ง ์ด ๊ฒฝ์ฐ์๋ ๋ถ๋ถ์ ์ผ๋ก Local Backtracking์ ์ฌ์ฉํ๋ ํธ์ด ๋ ํจ์จ์ ์ด๊ธฐ ๋๋ฌธ์๋ผ๊ณ ์ดํดํด์ฃผ์ธ์!
let fieldSeparator = Local { /\s{2,}|\t/ }
์ฌ์ฉ๋ฒ์ Local
๋ก ๊ธฐ์กด ํํ์์ ํ ๋ฒ ๊ฐ์ธ์ฃผ๋ฉด ๋ฉ๋๋ค. ์ฝ์ฃ ? ๐
Local
๋น๋๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ค ์ ์ด ํจ์จ์ ์ธ๊ฑธ๊น์?
Local
๋น๋์ ํฌํจ๋ ์ ๊ท์์ด ํ ๋ฒ ๋งค์นญ๋๋ฉด ๊ทธ ์ดํ์๋ ํด๋น ์ ๊ท์์ ๋ค์ ๊ฒ์ฌํ์ง ์์ต๋๋ค.
์ gif์์ ๋ณผ ์ ์์๋ ๋ฐฑํธ๋ํน์ด ์ผ์ด๋์ง ์๋๋ค๋ ๊ฑฐ์ฃ !
์ ํฌ๊ฐ ์ฌ์ฉํ ์์์ ๊ฒฝ์ฐ์๋ ํ๋์ ํ๋๊ฐ ์๋ก ํ์คํ๊ฒ ๊ตฌ๋ถ๋์ด ์๊ธฐ ๋๋ฌธ์ ์ด๋ฐ Local Backtracking์ด ๋ ํจ์จ์ ์ผ ์ ์์๋ ๊ฒ์ ๋๋ค.
์ฌ์ค ์ด Local
๋น๋๋ Regex์ **"atomic non-capturing group"**์ด๋ผ๋ ๊ธฐ๋ฅ์ ๋ํํ ๊ธฐ๋ฅ์
๋๋ค.
์ ํ์ด ์๊ฐํ๊ธฐ์ ๋ณด๊ธฐ๋ง ํด๋ ๋๋ ค์ด ์ด๋ฆ์ด๋ผ ์ข ๋ ์น๊ทผํ Local
์ด๋ผ๋ ์ด๋ฆ์ ๋ถ์ฌ์คฌ๋ค๊ณ ํด์.
์ ๋ฆฌํด๋ณด๋ฉด ์ด Local
์ด๋ผ๋ ๊ธฐ๋ฅ์ ์ ๊ท์์ ํํ์ scopeํํด์ ๋ถ๋ถ์ ์ผ๋ก ๋ฒ์๋ฅผ ์ค์ ํด์ค ์ ์๋ ๊ธฐ๋ฅ์ธ๊ฒ๋๋ค.
Swift Regex ํ์ฉํ๊ธฐ
์ฌ๊ธฐ๊น์ง ์ค์ ๋ถ๋ค ๊ณ ์ํ์ จ์ต๋๋ค. ๐
์ง๊ธ๊น์ง์ ๋ด์ฉ์ ๋ญ๊ฐ ํ์ฉํ๊ธฐ์๋ ๊ฐ ์ก๊ณ ์จ์ผํ ๋๋์ด ๊ฐํ๊ฒ ๋ค์ฃ ?
์ ํ์ ์ด ๊ฐ๋ ฅํ Regex์ ๊ธฐ๋ฅ์ ์ข ๋ ๊ณณ๊ณณ์์ ์ฌ์ฉํ ์ ์๋๋ก Swift standard library์ ์ด๊ณณ์ ๊ณณ์ ์นจํฌ์์ผ ๋์ต๋๋ค.
๊ทธ ์ ์ Regex
๋ฅผ ์ฌ์ฉํ๋ ์ต์ํ ์ธ ๊ฐ์ง ๋ฐฉ๋ฒ๋ถํฐ ๋น ๋ฅด๊ฒ ์ดํด๋ณด๋๊ฑธ๋ก ์์ํด๋ด
์๋ค!
Match
firstMatch(of:)
์ ๊ท์์ ๋งค์นญ๋๋ ๊ฐ์ฅ ์ฒซ Substring
์ ๋ฐํํฉ๋๋ค.
wholeMatch(of:)
์ ์ฒด String
์ ์ ๊ท์์ ๋งค์นญ์ํค๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค.
prefixMatch(of:)
String
์ prefix๋ก ์ ๊ท์์ด ๋งค์นญ๋๋ ๊ฒฝ์ฐ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค.
์ด ์ ์ ์๋ง ์ง๊ด์ ์ผ๋ก ๋ค๋ค ์ ์ฌ์ฉํ์ค ์ ์์ ๊ฒ ๊ฐ์์ ๐
Swift Standard Library API
๊ฟ์ด ๊ฐ๋ํ ๊ตฌ๊ฐ์ ๋๋ค ๐
๋น๊ต์ ๊ฐ๋จํ๊ฒ ์ ๊ท์์ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ๋ค์ด๊ฑฐ๋ ์.
starts(with regex:)
prefixMatch(of:)
์ ๋น์ทํ ๊ธฐ๋ฅ์ ํ๋๋ฐ์, ์ด ๊ฒฝ์ฐ์๋ ๋งค์นญ ๋ฐ์ดํฐ ์์ด ๊ณง๋ฐ๋ก Bool
๊ฐ์ ๋ฐํํด์ค๋๋ค.
์ ๊ท์ ํํ์ ๋ง๋ ๊ฐ์ผ๋ก ์์ํ๋์ง ์ฌ๋ถ๋ง ๊ฒ์ฌํ๊ณ ์ถ์ ๋ ์ ์ฉํ๊ฒ ์ฃ ?
replacing(_ regex:, with replacement:)
์ ๊ท์ ํํ์ ๋ง๋ ๋ถ๋ถ์ replacement
์ ์ฃผ์ด์ง ๋ฌธ์์ด๋ก ๊ต์ฒดํฉ๋๋ค. ๋๊ฒ ์ ์ฉํ๊ฒ ์ฐ์ผ ์ ์์ ๊ฒ ๊ฐ์ ๋๋์ด ๋ง์ด ๋๋ API๋ค์.
trimmingPrefix(_ regex:)
String
์ prefix๊ฐ ์ ๊ท์ ํํ์ ๋งค์นญ๋๋ค๋ฉด ๊ทธ ๊ฐ์ ์ ๊ฑฐํฉ๋๋ค.
trimmingSuffix
๋ ์๋ค๋ ์ ์ด ์กฐ๊ธ ์์ฝ๊ตฐ์..
split(separator:)
์ ๊ท์์ ๋งค์นญ๋๋ ๋ถ๋ถ์ ๊ธฐ์ค์ผ๋ก String
์ split
ํฉ๋๋ค.
iOS 16.0 ์ด์์์๋ง ์ธ ์ ์๋ค๋ ์ ์ด ์์ฌ์ธ ์ ๋๋ก ์ ์ฉํ ๊ธฐ๋ฅ๋ค์ธ ๊ฒ ๊ฐ์์..
์๋๋ผ๋ฉด์ ๋ฌธ์์ด์ ์ชผ๊ฐ๊ณ ๊ต์ฒดํ๊ณ ๋ถ์ด๊ณ ํ๋ ๋ฑ์ ์์ ๋ค์ ์ ๊ท์ ํ๋๋ก ์ฒ๋ฆฌํด์ค ์ ์๋๊ฑฐ๋๊น์..!
๊ทธ๋ฆฌ๊ณ ์ ํ์ด ์ ๊ณตํ๋ค๋ API๊ฐ ํ๋ ๋ ์๋๋ฐ์, ๋ฐ๋ก ํจํด ๋งค์นญ ๋ฌธ๋ฒ์ ์ฌ์ฉํ ์ ์๋ค๋ ์ ์ ๋๋ค.
switch "abc" {
case /\w+/:
print("It's a word!")
default:
print("It's not a word..")
}
์ ๋ง ๋๋ฌด ์ ์ฉํด๋ณด์ด์ฃ ใ ใ
๊ทผ๋ฐ ์ ์ ๋ ์๋ ๊น์..
ํํ..
์ฌ๊ธฐ๋ฅผ ๋ณด๋ฉด ์ ๋ง ๊ฒช๋ ๋ฌธ์ ๋ ์๋ ๊ฒ ๊ฐ๊ธด ํฉ๋๋ค๋ง.. ์ ์ฉ์ด ์๋ ๋ด์ฉ์ WWDC ์ธ์
์์ ์๊ฐํ์๋ฆฌ๊ฐ ์๋ ๋ฐ ๋ง์ด์ฃ .
์ด ๋ถ๋ถ๋ ์ถ๊ฐ์ ์ผ๋ก ์๊ฒ๋๋ ๋ด์ฉ์ด ์์ผ๋ฉด ์ถ๊ฐํด๋๋๋ก ํ๊ฒ ์ต๋๋ค...
๋ถ์คํธ์บ ํ ๋๋ฃ๋ถ๋ค์ด ๊ด๋ จ ๋ด์ฉ์ ์ฐพ์์ฃผ์ จ์ต๋๋ค!
Swift ์ปค๋ฎค๋ํฐ ํฌ๋ผ์ ๋ณด์๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
switch "abcde" {
case /abc/: // I'd expect this regular expression
case /.*abc.*/: // to work like this one
case /^abc$/: // rather than this one
default:
}
/abc/
์ ๊ธฐ๋ฅ์ด ๋ฌธ์์ด์ ๋ถ๋ถ๋ง ์ถฉ์กฑํด๋ case๋ก ์ฒ๋ฆฌํ ์ง, ํน์ ์ ์ฒด ๋ฌธ์์ด์ด ๋งค์นญ๋์ด์ผ case๋ก ์ฒ๋ฆฌํ ์ง์ ๋ํ ํ ๋ก ์ด ์์๋ ๊ฒ ๊ฐ์ต๋๋ค.
firstMatch
์ wholeMatch
์ค ์ด๋ค ๋งค์นญ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ง๋ฅผ ๊ณ ๋ฏผํ์
จ๋ ๊ฒ์ด์ฃ .
Perl์ด๋ Ruby๊ฐ์ ๋ค๋ฅธ ์ธ์ด๋ค์ ๊ฒฝ์ฐ๋ค์ ์ฐธ๊ณ ํด๊ฐ๋ฉฐ ํ ๋ก ํ์ ๊ฒ ๊ฐ์ง๋ง, ์์ฝ๊ฒ๋ ๊ฒฐ๋ก ์ด ์ง์ด์ง์ง ๋ชปํด ์ด ๊ธฐ๋ฅ์ ๊ตฌํ๋์ง ๋ชปํ ๊ฒ์ผ๋ก ๋ณด์ด๋ค์.
WWDC ์ธ์ ์ ๋ฐํ๊น์ง ๋์ง๋ง ์ปค๋ฎค๋ํฐ์ ์ํด ๊ตฌํ์ด ๋์ง ์์ ๊ธฐ๋ฅ๋ ์์ ์ ์๋ค๋ ๊นจ๋ฌ์์ ์ป์ ์ ์์์ต๋๋ค. ๐
์ ์ด๋ ์ จ๋์..?
iOS 16๋ถํฐ ์ฌ์ฉํ ์ ์๋ค๋ ์ ์ด ๋๋ฌด ์์ฝ์ง๋ง.. ๊ทธ๋๋ ์ด์ ์ ๊ท์ ์ข ์ธ ์ค์ ์๋ค๋ ์ ์ฅ์์ ์ด๋ฐ ๊ธฐ๋ฅ๋ค์ ์์๋ค๊ณ ํ์ํ ๋ด์ฉ์ธ ๊ฒ ๊ฐ์์ ๐
์ ์ฌ์ ์์ ์ ๊ท์์ ์ฌ์ฉํ ์ค ์๋ ๋ฉ์ง ๊ฐ๋ฐ์๊ฐ ๋ ๊ฒ ๊ฐ์์ (์์ง ํ๋ก์ ํธ์ ์ ์ฉํด๋ณธ ์ ์์)
์์ฃผ ๋ฟ๋ฏํ๊ตฐ์! ๐ฅฐ
References