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/

0b26590a0a8b0f1da140ed5de9b68379?s=128

USAMI Kosuke

June 24, 2022
Tweet

More Decks by USAMI Kosuke

Other Decks in Programming

Transcript

  1. Meet Swift Regex WWDC22 セッション要約 宇佐見公輔 / 株式会社ゆめみ

  2. 自己紹介 宇佐見公輔 / 株式会社ゆめみ iOSDC Japan 2022 で記事(x2 )を書き、トークをします。 来週6/27

    (月)にも、YUMEMI.swift #15 でSwift Regex の話をします。
  3. Meet Swift Regex Swift 5.7 で、正規表現を扱う Regex 型が追加される。 注意:セッション中のコードはビルドできないものがいくつかある。 `

    `
  4. 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
  5. 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"] ` `
  6. 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"]
  7. struct Regex Regex の生成方法は以下がある。 Regex literals Runtime construction Regex builders

  8. struct Regex // Regex リテラル let digits = /\d+/ //

    実行時に生成 let runtimeString = #"\d+"# // 補足 : #" 〜"# はSwift の文字列リテラルの一種 let digits = try Regex(runtimeString) // Regex ビルダー import RegexBuilder let digits = OneOrMore(.digit)
  9. Swift Regex 以下の4 つの強みがある。 簡明なリテラルと構造的なビルダーが使える。 正規表現の一部に既存のパーサーが使える。 Unicode を扱える。 Predictable execution,

    prominent controls
  10. 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"))) }
  11. 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")) }
  12. 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 // ... }
  13. 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 ` ` ` ` ` ` ` `
  14. 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!") } }
  15. 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 }
  16. Unicode switch (" 🧟‍♀️💖🧠", "The Brain Cafe\u{301}") { case (/.\N{SPARKLING

    HEART}./, /.*café/.ignoresCase()): print("Oh no! 🧟‍♀️💖🧠, but 🧠💖☕️ !") default: print("No conflicts found") }
  17. 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 // ... }
  18. Local 上述の正規表現は、セパレータの処理に無駄がある。 例えば、8 つの空白を 見つけた場合、次のようになる(グローバルバックトラッキング)。 8 つの空白の残りが正規表現にマッチするか調べる。 マッチしなければ、次に7 つの空白の残りがマッチするか調べる。 マッチしなければ、次に6

    つの空白の残りがマッチするか調べる。 これを避けるため、 Local を使うことができる。 ` ` let fieldSeparator = Local { /\s{2,}|\t/ }
  19. 次に見るセッション より詳しくは「Swift Regex: Beyond the basics 」のセッションを見よ。