Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Meet Swift Regex

Meet Swift Regex

Meet Swift Regex : WWDC22セッション要約

登壇
https://www.youtube.com/watch?v=IpQAovAzwf8&t=3980s

集まれSwift好き!Swift愛好会スピンオフ WWDC22セッション要約会 @オンライン
https://love-swift.connpass.com/event/247317/

USAMI Kosuke

June 24, 2022
Tweet

More Decks by USAMI Kosuke

Other Decks in Programming

Transcript

  1. Scenario: Financial investigation 以下のような文字列データ(金融取引の記録)を処理したい。 取引の種類、取引日、個人または機関名、金額 各フィールドは2 つ以上のスペースまたはタブで区切られる。 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
  2. Processing collections split で分割してはどうか。 ["Doug\'s", "Dugout", "Dogs"] は意図しない分割。 「2 つ以上のスペースまたはタブ」で区切りたい。

    そこで正規表現を使う。 ` ` let transaction = "DEBIT 03/05/2022 Doug's Dugout Dogs $33.27" let fragments = transaction.split(whereSeparator: \.isWhitespace) // ["DEBIT", "03/05/2022", "Doug\'s", "Dugout", "Dogs", "$33.27"] ` `
  3. Processing strings Regex リテラルが使える split(by:) が追加された。 ` ` let transaction

    = "DEBIT 03/05/2022 Doug's Dugout Dogs $33.27" let fragments = transaction.split(by: /\s{2,}|\t/) // ["DEBIT", "03/05/2022", "Doug's Dugout Dogs", "$33.27"]
  4. struct Regex // Regex リテラル let digits = /\d+/ //

    実行時に生成 let runtimeString = #"\d+"# // 補足 : #" 〜"# はSwift の文字列リテラルの一種 let digits = try Regex(runtimeString) // Regex ビルダー import RegexBuilder let digits = OneOrMore(.digit)
  5. Create a Regex builder import RegexBuilder import Foundation 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"))) }
  6. Capture 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")) }
  7. Capture Capture した部分は、次のように取り出せる。 let transaction = "DEBIT 03/05/2022 Doug's Dugout

    Dogs $33.27" let match = transaction.wholeMatch(of: transactionMatcher) { let (kind, date, institution, amount) = match.output // ... }
  8. Plot twist 金額の単位に応じて、日付部分のパース処理を変えたい。 金額が $ の場合は month/day/year とみなす。 金額が £

    の場合は day/month/year とみなす。 DEBIT 03/05/2022 Doug's Dugout Dogs $33.27 DEBIT 06/03/2022 Oxford Comma Supply Ltd. £57.33 ` ` ` ` ` ` ` `
  9. Plot twist Foundation の Date パース時のstrategy 機能を活用する。 ` ` 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!") } }
  10. Plot twist let regex = #/ (?<date> \d{2} / \d{2}

    / \d{4}) (?<middle> \P{currencySymbol}+) (?<currency> \p{currencySymbol}) /# ledger.replace(regex) { match -> String in let date = try! Date(String(match.date), strategy: pickStrategy(match.currency)) let newDate = date.formatted(.iso8601.year().month().day()) return newDate + match.middle + match.currency }
  11. Unicode switch (" 🧟‍♀️💖🧠", "The Brain Cafe\u{301}") { case (/.\N{SPARKLING

    HEART}./, /.*café/.ignoresCase()): print("Oh no! 🧟‍♀️💖🧠, but 🧠💖☕️ !") default: print("No conflicts found") }
  12. TryCapture TryCapture でCapture したデータを変換できる。 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 // ... }