Published on

๐ŸŽ Swift - Regex.02

Authors
  • avatar
    Name
    ์ด์ฐฝ์ค€
    Twitter

Regex์˜ ๊ฐœ๋…์ ์ธ ๋‚ด์šฉ๋“ค์„ ์•Œ์•˜์œผ๋‹ˆ, ์ด์ œ Swift์—์„œ๋Š” ์ด Regex๋ฅผ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ด…์‹œ๋‹ค.

๐ŸŽ Swift - Regex.01

๋ชป ๋ณด์‹  ๋ถ„๋“ค์€ ์œ„ ํฌ์ŠคํŠธ๋กœ!

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]*)์˜ ๊ฒฐ๊ณผ๋Š” ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

RegexWith*

*๋Š” ์—†๊ฑฐ๋‚˜ ํ•œ๊ฐœ๊ฑฐ๋‚˜ ๋ฌดํ•œ๋Œ€์˜ ๊ฐœ์ˆ˜๋ฅผ ๊ฐ€์งˆ ๋•Œ ๋งค์นญ๋˜์ฃ ?

"์—†์„ ๋•Œ" ๋„ ๋งค์นญ๋˜๊ธฐ ๋•Œ๋ฌธ์— "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: ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋“ค์–ด๊ฐˆ ๊ฐ’์ธ๋ฐ์š”..

NSRegularExpression
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์˜ ํŠน์ง•์„ ์•Œ์•„๋ด…์‹œ๋‹ค.

  1. Regex Builder๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋“ฏ์ด ๊ตฌ์กฐํ™”๋˜๊ณ  ์ •๋ˆ๋œ ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  2. ์‹ค์ œ Parser๋ฅผ ์ •๊ทœ์‹์˜ ๊ตฌ์„ฑ์š”์†Œ๋กœ์„œ ๊ฐ€์ ธ๋‹ค ์“ธ ์ˆ˜ ์žˆ๋‹ค.
  3. Unicode๋ฅผ ์ง€์›ํ•ด ์–ด๋–ค ์–ธ์–ด๋˜์ง€ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋‹ค.
  4. ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์‹คํ–‰ ๋ฐฉ์‹์„ ์ œ๊ณตํ•ด ์–ด๋–ค ๋ถ€๋ถ„์ด ๋ฌธ์ œ์ธ์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์‰ฝ๋‹ค.

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์˜ ์™ผ์ชฝ๋ถ€ํ„ฐ ์˜ค๋ฅธ์ชฝ ์ˆœ์„œ๋Œ€๋กœ ์ปดํฌ๋„ŒํŠธํ™” ์‹œ์ผœ์„œ ๊ฒ€์‚ฌ๋ฅผ ํ•œ๋‹ค๋‹ˆ.. ์•„์ฃผ ์ง๊ด€์ ์ด๊ตฐ์š”...

์ด์ œ ์ปดํฌ๋„ŒํŠธ๋ณ„๋กœ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ด…์‹œ๋‹ค.

whitespace

๋จผ์ € fieldSeparator๋Š” space ๊ธฐ์ค€ 2์นธ ์ด์ƒ์˜ whitespace๋ฅผ ๋งค์นญํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ํ˜•ํƒœ์˜ ์ •๊ทœ์‹์œผ๋กœ ์ •์˜๋˜์–ด ์žˆ๋„ค์š”.

๊ฐ๊ฐ์˜ ํ•„๋“œ๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•ด์ฃผ๊ณ  ์žˆ๋Š” ๊ฒƒ ๊ฐ™์ฃ ?

CREDIT_DEBIT

CREDIT๊ณผ DEBIT๋„ ์‰ฝ์Šต๋‹ˆ๋‹ค.

๋‘ ๊ฐ€์ง€ ๊ฒฝ์šฐ์˜ ์ˆ˜ ๋ฐ–์— ์—†๊ธฐ ๋•Œ๋ฌธ์— CREDIT|DEBIT์ด๋ฉด ์ถฉ๋ถ„ํ•˜์ฃ .

Date

๋‚ ์งœ๋ฅผ ํŒŒ์‹ฑํ•  ๋•Œ Regex Builder์˜ ํŠน์ง•์ด ์ž˜ ๋“ค์–ด๋‚ฉ๋‹ˆ๋‹ค.

"์‹ค์ œ Parser๋ฅผ ์ •๊ทœ์‹์˜ ๊ตฌ์„ฑ์š”์†Œ๋กœ์„œ ๊ฐ€์ ธ๋‹ค ์“ธ ์ˆ˜ ์žˆ๋‹ค."๋ผ๋Š” ํŠน์ง•์ด ์žˆ์—ˆ์ฃ ?

String ๊ฐ’์„ One์ด๋ผ๋Š” Regex ๊ตฌ์กฐ์ฒด์™€ .date๋ผ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๋‚ ์งœ ํ˜•์‹์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป ์ž…๋‹ˆ๋‹ค.

DateFormat

.numeric ์™ธ์— ๋‹ค๋ฅธ ํ˜•์‹์œผ๋กœ ํŒŒ์‹ฑ๋„ ๊ฐ€๋Šฅํ•˜๊ณ , locale๊ณผ timeZone๋„ ์„ค์ •์ด ๊ฐ€๋Šฅํ•œ ์ ์€ ๊ตณ์ด ์„ค๋ช…ํ•˜์ง€ ์•Š์•„๋„ ์•Œ ์ˆ˜ ์žˆ์ฃ ! ๐Ÿ˜

name

์ œ์ผ ๋ณต์žกํ•ด๋ณด์ด๋Š” ์„ธ ๋ฒˆ์งธ ํ•„๋“œ์ž…๋‹ˆ๋‹ค.

OneOrMore { CharacterClass.any }

์ด๋ฏธ ์ฃผ์–ด์ง„ ์ •๋‹ต ๋Œ€์‹  ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๊ฒฐ๊ณผ๋Š” ์–ด๋–ป๊ฒŒ ๋‚˜์˜ฌ๊นŒ์š”?

๋‹ต์€ "๋™์ผํ•˜๋‹ค." ์ž…๋‹ˆ๋‹ค.

์–ด๋–ค ์บ๋ฆญํ„ฐ๋˜์ง€ ํ•˜๋‚˜ ํ˜น์€ ๊ทธ ์ด์ƒ์„ ์ฐพ์•„๋‚ด๊ณ , ๋ฐ”๋กœ ๋‹ค์Œ์— fieldSeparator๊ฐ€ ๋‹ค์‹œ ์˜ค๊ธฐ ๋•Œ๋ฌธ์— whitespace๋“ค ์ „๊นŒ์ง€์˜ ๊ฐ’๋งŒ ์ •์ƒ์ ์œผ๋กœ ๋งค์นญ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ •๋‹ต์—์„œ ํ›จ์”ฌ ๋ณต์žกํ•ด๋ณด์ด๋Š”

OneOrMore {
  NegativeLookahead { fieldSeparator }
  CharacterClass.any
}

์„ ์‚ฌ์šฉํ•œ ์ด์œ ๋Š” ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.

CharacterClass.any

Regex Builder๋Š” fieldSeparator๋ฅผ ๋งŒ๋‚˜๊ธฐ ์ „๊นŒ์ง€๋Š” OneOrMore { CharacterClass.any }์— ๋งค์นญ๋˜๋Š” String์˜ ๋๊นŒ์ง€๋ฅผ ๋งค์นญ ๋ฒ”์œ„๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ดํ›„์— fieldSeparator๋ฅผ ๋งŒ๋‚œ ํ›„์— ๋๊นŒ์ง€ ์ƒ๊ฐํ•œ ๋งค์นญ ๋ฒ”์œ„์—์„œ ํ•˜๋‚˜์”ฉ ๋ฒ”์œ„๋ฅผ ์ค„์—ฌ๋‚˜๊ฐ€๋ฉฐ ์ƒˆ๋กœ์šด ์กฐ๊ฑด์— ๋งค์นญ๋˜๋Š” ์š”์†Œ๋ฅผ ์ฐพ์•„๋‚˜๊ฐ€๋Š” ๊ฒƒ์ด์ฃ .

์ „๋ถ€ ์žก์•„๋†“๊ณ  ํ•˜๋‚˜์”ฉ ์ค„์—ฌ๊ฐ€๋ฉฐ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค๋Š” ์• ์ดˆ์— ์ฒ˜์Œ๋ถ€ํ„ฐ ์ž˜ ์žก์•„๋‘๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ด๊ฒ ์ฃ ?

์ •๋‹ต์˜ ์˜ˆ์‹œ๋Š” NegativeLookahead๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค์Œ ๊ฐ’์ด fieldSeparator์ธ์ง€๋ฅผ ๊ฒ€์‚ฌํ•˜๋ฉฐ ๋งค์นญ๋˜๋Š” ์š”์†Œ๋ผ๋ฉด ์ค‘๋‹จํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•œ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.

NegativeLookahead๊ฐ€ ๋ญ”๊ฐ€์š”?

Lookahead

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์˜ ๊ธฐ๋ณธ ์˜ต์…˜์„ ๋ณ€๊ฒฝํ•ด์คŒ์œผ๋กœ์จ ์ ์šฉํ•ด์ค„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.


LocalizedCurrency

๋งˆ์ง€๋ง‰ ๊ธˆ์•ก ๋ถ€๋ถ„์€ 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}์ด ๋Œ€์ฒด ๋ญ”๋ฐ ํ•˜๋ฉด์„œ ์ด๊ฒƒ์ €๊ฒƒ ์ฐพ์•„๋ณด๋˜ ์ค‘...

UnicodeGroup

์˜ˆ.. Regex์— ์›๋ž˜ ์žˆ๋Š” ํ‘œํ˜„์ด์˜€์Šต๋‹ˆ๋‹ค. (์ถœ์ฒ˜๋Š” ์• ํ”Œ ๊ณต์‹ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค.)

Unicode์—์„œ ์ •์˜ํ•œ ํ”„๋กœํผํ‹ฐ์— ์†ํ•ด์žˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•˜๋Š” ํ‘œํ˜„๋“ค์ด๋ผ๊ณ  ํ•˜๋„ค์š”.

\p{}๊ฐ€ ํ”„๋กœํผํ‹ฐ์— ์†ํ•ด์žˆ๋Š”์ง€์ด๊ณ  \P{}๋Š” ํ”„๋กœํผํ‹ฐ์— ์†ํ•ด์žˆ์ง€ ์•Š์€์ง€๋ฅผ ๊ฒ€์‚ฌํ•˜๋Š” ํ‘œํ˜„์ด๋ผ๊ณ  ํ•ด์š”.

์ง€์ •๋œ ์œ ๋‹ˆ์ฝ”๋“œ ํ”„๋กœํผํ‹ฐ๋“ค์€ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทผ๋ฐ์š”.. ๋˜ currencySymbol์€ ๊ทธ๋ฃน ์ด๋ฆ„์— ์—†๋”๋ผ๊ณ ์š”..?

๋Œ€์‹  ๋น„์Šค๋ฌด๋ฆฌํ•œ ๊ฑด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

\\p\{Sc\} or \\p\{Currency_Symbol\} : any currency sign.

์• ํ”Œ์ด ๋˜ ๋‚˜๋ฆ„๋Œ€๋กœ ๋ž˜ํ•‘์„ ํ•œ ๊ฑด์ง€ ๋ญ”์ง€... ์•Œ ์ˆ˜๊ฐ€ ์—†๊ตฐ์š”.

๋ญ”๊ฐ€ ์•Œ์•„๋‚ด๋ฉด ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•ด๋†“๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Unicode.GeneralCategory

ํ•˜ํ•˜ ๐Ÿ˜ฒ Swift์— ์ •์˜๋˜์–ด ์žˆ๋Š”๊ฒŒ ๋งž๋”๊ตฐ์š”!

SwiftUnicode

Unicode ๋ฌธ์„œ๋ฅผ ๋ณด์‹œ๋ฉด ๋ฏธ๋ฆฌ ๋ถ„๋ฅ˜๋œ case๋“ค์ด enum์œผ๋กœ ์ •์˜๋˜์–ด ์žˆ๊ณ ..

currencySymbol

๊ทธ ์ค‘ 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๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ €์žฅํ•ด์ฃผ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

NewDateLedger

์œ„์˜ ํ˜•ํƒœ์—์„œ ์•„๋ž˜ ํ˜•ํƒœ๋กœ์š”!

์—ฌ๊ธฐ๊นŒ์ง€ ๋ชจ๋“  ๊ฒŒ ์™„๋ฒฝํ•ด ๋ณด์ด์ง€๋งŒ, ํ•œ ๊ฐ€์ง€ ๊ณ ๋ คํ•  ์ ์ด ํ•˜๋‚˜ ๋” ์žˆ์Šต๋‹ˆ๋‹ค.

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๊ฐ€ ๋ฌธ์ œ๊ฐ€ ๋œ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

Global Backtracking

์ดํ›„์— ์˜ค๋Š” ํŒจํ„ด์ด ์‹คํŒจํ•˜๋ฉด, ๋งค์นญ๋˜์—ˆ๋˜ 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..")
}

์ •๋ง ๋„ˆ๋ฌด ์œ ์šฉํ•ด๋ณด์ด์ฃ  ใ…Žใ…Ž

๊ทผ๋ฐ ์™œ ์ €๋Š” ์•ˆ๋ ๊นŒ์š”..

Regex Pattern Matching

ํ•˜ํ•˜..

์—ฌ๊ธฐ๋ฅผ ๋ณด๋ฉด ์ €๋งŒ ๊ฒช๋Š” ๋ฌธ์ œ๋„ ์•„๋‹Œ ๊ฒƒ ๊ฐ™๊ธด ํ•ฉ๋‹ˆ๋‹ค๋งŒ.. ์ ์šฉ์ด ์•ˆ๋œ ๋‚ด์šฉ์„ WWDC ์„ธ์…˜์—์„œ ์†Œ๊ฐœํ–ˆ์„๋ฆฌ๊ฐ€ ์—†๋Š” ๋ฐ ๋ง์ด์ฃ .

์ด ๋ถ€๋ถ„๋„ ์ถ”๊ฐ€์ ์œผ๋กœ ์•Œ๊ฒŒ๋˜๋Š” ๋‚ด์šฉ์ด ์žˆ์œผ๋ฉด ์ถ”๊ฐ€ํ•ด๋‘๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค...

๋ถ€์ŠคํŠธ์บ ํ”„ ๋™๋ฃŒ๋ถ„๋“ค์ด ๊ด€๋ จ ๋‚ด์šฉ์„ ์ฐพ์•„์ฃผ์…จ์Šต๋‹ˆ๋‹ค!

SE-0357: Regex String Processing Algorithms [Accepted with modifications] SE-0357: Regex String Processing Algorithms

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

_์Šˆํ”„๋ฆผ - [Swift] ์ฝ”๋”ฉํ…Œ์ŠคํŠธ ๋ณด๋‹ค๊ฐ€ ์—ด ๋ฐ›์•„์„œ ์ •๋ฆฌํ•˜๋Š” Swift ์ •๊ทœ์‹ - NSRegularExpression (Regex)

ZeddiOS - iOS) NSRegularExpression.Options

WWDC22 - Meet Swift Regex

์—˜๋ฐ”๋…ธํ”„ ์ŠคํŠœ๋””์˜ค ๊ณต์‹ ๋ธ”๋กœ๊ทธ ๊ฒธ ๋‚™์„œ์žฅ - ์ •๊ทœํ‘œํ˜„์‹ Lookahead, Lookbehind ๊ธฐ๋Šฅ ์ดํ•ดํ•˜๊ธฐ

Unicode Regular Expressions

WWDC22 - Swift Regex: Beyond the basics