Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 宇佐見公輔 / 株式会社ゆめみ iOSDC Japan 2022 で記事(x2 )を書き、トークをします。 来週6/27 (月)にも、YUMEMI.swift #15 でSwift Regex の話をします。

Slide 3

Slide 3 text

Meet Swift Regex Swift 5.7 で、正規表現を扱う Regex 型が追加される。 注意:セッション中のコードはビルドできないものがいくつかある。 ` `

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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"] ` `

Slide 6

Slide 6 text

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"]

Slide 7

Slide 7 text

struct Regex Regex の生成方法は以下がある。 Regex literals Runtime construction Regex builders

Slide 8

Slide 8 text

struct Regex // Regex リテラル let digits = /\d+/ // 実行時に生成 let runtimeString = #"\d+"# // 補足 : #" 〜"# はSwift の文字列リテラルの一種 let digits = try Regex(runtimeString) // Regex ビルダー import RegexBuilder let digits = OneOrMore(.digit)

Slide 9

Slide 9 text

Swift Regex 以下の4 つの強みがある。 簡明なリテラルと構造的なビルダーが使える。 正規表現の一部に既存のパーサーが使える。 Unicode を扱える。 Predictable execution, prominent controls

Slide 10

Slide 10 text

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"))) }

Slide 11

Slide 11 text

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")) }

Slide 12

Slide 12 text

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 // ... }

Slide 13

Slide 13 text

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 ` ` ` ` ` ` ` `

Slide 14

Slide 14 text

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!") } }

Slide 15

Slide 15 text

Plot twist let regex = #/ (? \d{2} / \d{2} / \d{4}) (? \P{currencySymbol}+) (? \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 }

Slide 16

Slide 16 text

Unicode switch (" 🧟‍♀️💖🧠", "The Brain Cafe\u{301}") { case (/.\N{SPARKLING HEART}./, /.*café/.ignoresCase()): print("Oh no! 🧟‍♀️💖🧠, but 🧠💖☕️ !") default: print("No conflicts found") }

Slide 17

Slide 17 text

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 // ... }

Slide 18

Slide 18 text

Local 上述の正規表現は、セパレータの処理に無駄がある。 例えば、8 つの空白を 見つけた場合、次のようになる(グローバルバックトラッキング)。 8 つの空白の残りが正規表現にマッチするか調べる。 マッチしなければ、次に7 つの空白の残りがマッチするか調べる。 マッチしなければ、次に6 つの空白の残りがマッチするか調べる。 これを避けるため、 Local を使うことができる。 ` ` let fieldSeparator = Local { /\s{2,}|\t/ }

Slide 19

Slide 19 text

次に見るセッション より詳しくは「Swift Regex: Beyond the basics 」のセッションを見よ。