ハイライト
Swift 5.7 では言語レベルで Regex が導入され、サポートされています。
/.../3 つの作成方法: リテラル、ランタイム構築、および Regex Builder、強力な型キャプチャ、Foundation パーサー埋め込み、Unicode 正しい処理、およびローカル トレースバック制御などの高度な機能。
主要内容
文字列解析の問題点
あなたは財務調査員と協力して取引分析ツールを開発しています。彼らがあなたに与えるデータは、プレーンテキスト文字列の束です:
DEBIT 03/05/2022 Doug's Dugout Dogs $33.27
フィールドは「3 つ以上のスペース」または「タブ」で区切られていますが、その理由は誰も覚えていません。日付の形式があいまいなので、月/日/年であることを祈るしかありません。
(01:16)String設定されたアルゴリズムを試してみますか?split(whereSeparator: \.isWhitespace)「Doug’s Dugout Dogs」を 3 つのフィールドに分割します。インデックスレベルにドロップダウンして手動で解析しますか?コードは冗長でエラーが発生しやすくなります。
var slice = transaction[...]
func extractField() -> Substring {
let endIdx = {
var start = slice.startIndex
while true {
guard let spaceIdx = slice[start...].firstIndex(where: \.isWhitespace) else {
return slice.endIndex
}
if slice[spaceIdx] == "\t" {
return spaceIdx
}
let afterSpaceIdx = slice.index(after: spaceIdx)
if afterSpaceIdx == slice.endIndex || slice[afterSpaceIdx].isWhitespace {
return spaceIdx
}
start = afterSpaceIdx
}
}()
defer { slice = slice[endIdx...].drop(while: \.isWhitespace) }
return slice[..<endIdx]
}
let kind = extractField()
let date = try Date(String(extractField()), strategy: Date.FormatStyle(date: .numeric))
let account = extractField()
let amount = try Decimal(String(extractField()), format: .currency(code: "USD"))
このコードは 20 行を超えており、フィールド分割のみを処理します。より良いツールが必要です。
正規表現リテラル: 簡潔な構文
(02:47) Swift 5.7 の導入/.../正規表現リテラル構文は、Perl、Python、Ruby、Java などの主流の正規言語と互換性があります。
let digits = /\d+/
// digits: Regex<Substring>
キーポイント:
- コンパイラは通常の構文を理解し、構文の強調表示とコンパイル時のエラー チェックを提供します。
- 出力タイプは汎用です
Regex<Output>、キャプチャ タイプ情報が含まれます
まず正規表現を使用してフィールド分割の問題を解決します。
let transaction = "DEBIT 03/05/2022 Doug's Dugout Dogs $33.27"
let fragments = transaction.split(separator: /\s{2,}|\t/)
// ["DEBIT", "03/05/2022", "Doug's Dugout Dogs", "$33.27"]
(03:56)\s{2,}2 つ以上の空白文字と一致します。|または、\tタブ文字と一致します。 1 行のコードで 20 行を超えるインデックス付けが置き換えられます。
まだ利用可能replacing区切り文字を統一します。
let normalized = transaction.replacing(/\s{2,}|\t/, with: "\t")
// DEBIT\t03/05/2022\tDoug's Dugout Dogs\t$33.27
Regex Builder: 宣言型文字列処理
(03:44) リテラルは簡潔ですが、複雑な正規表現は読みにくいです。 Regex Builder は宣言構文を提供します。
import RegexBuilder
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 { ... }SwiftUI の ViewBuilder に似た宣言型ビルダーです- リテラルを混在させることができます (
/CREDIT|DEBIT/) とビルダーコンポーネント One(.date(...))Foundation の日付パーサーに直接埋め込むOne(.localizedCurrency(...))通貨パーサーを埋め込む
(06:55)NegativeLookahead重要なコンポーネントです。次の入力を「覗き見」しますが、それを消費することはなく、区切り文字が見つかったときにフィールドの内容の一致が停止されるようにします。
OneOrMore {
NegativeLookahead { fieldSeparator } // 偷看:下一个不是分隔符才继续
CharacterClass.any // 匹配任意字符
}
いいえNegativeLookahead,CharacterClass.any行末まで貪欲に一致させてから、最後までバックトラックするため、非効率的です。
強い型のキャプチャ
(09:04)Capture関心のあるフィールドを抽出すると、タイプが直接反映されます。Regex汎用パラメータでは次のようになります。
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)>
キーポイント:
- 0 番目のキャプチャは、一致したテキスト全体です (
Substring) - 1 番目のキャプチャはトランザクション タイプ (
Substring) - 2 番目のキャプチャは日付 (
Date、Foundation パーサーによって直接出力されます) - 3 番目のキャプチャは施設名 (
Substring) - 4回目の捕獲量は(
Decimal、Foundation パーサーによって直接出力されます)
以下を使用する場合は、厳密に型指定されたプロパティに直接アクセスします。
func parseLine(_ line: Substring) throws -> MailmapEntry {
let regex = /\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/
guard let match = line.prefixMatch(of: regex) else {
throw MailmapError.badLine
}
return MailmapEntry(name: match.1, email: match.2)
}
実行時の構築および拡張の区切り文字
(03:20) 一部の正規表現は、実行時にユーザー入力から構築する必要があります。
let runtimeString = #"\d+"#
let digits = try Regex(runtimeString)
// digits: Regex<AnyRegexOutput>
キーポイント:
- ランタイム構造が構文エラーをスローする可能性がある
- 出力タイプは次のとおりです。
AnyRegexOutput、コンパイル時にはキャプチャ情報が不明であるため、
(10:53) スラッシュを含む正規表現の場合は、拡張区切り文字を使用してください#/.../#:
let regex = #/
(?<date> \d{2} / \d{2} / \d{4})
(?<middle> \P{currencySymbol}+)
(?<currency> \p{currencySymbol})
/#
// Regex<(Substring, date: Substring, middle: Substring, currency: Substring)>
キーポイント:
#/.../#正規表現内でエスケープされていないスラッシュを許可する- 拡張構文モードでは空白は無視され、読みやすさを向上させるためにインデントを使用できます。
(?<name>...)これは名前付きキャプチャであり、出力タイプには次のラベルが付いています。
Unicode は正しく処理されます
(12:45) Swift の String は、デフォルトで Unicode 拡張書記素クラスター (文字レベル) を処理します。正規表現もこのモデルに従います。
let aZombieLoveStory = "🧟♀️💖🧠"
// Characters: 🧟♀️, 💖, 🧠
aZombieLoveStory.unicodeScalars
// U+1F9DF, U+200D, U+2640, U+FE0F, U+1F496, U+1F9E0
(14:12) Swift 文字列は Unicode の正規同等性に従います。"café"そして"cafe\u{301}"基になる Unicode スカラーが異なっていても、比較の結果は等しくなります。
正規表現はデフォルトで文字レベルでも一致します。
input.firstMatch(of: /.\N{SPARKLING HEART}./)
// 🧟♀️💖🧠 — 匹配三个字符
input.firstMatch(of: /.\N{SPARKLING HEART}./.matchingSemantics(.unicodeScalar))
// ️💖🧠 — 匹配三个 Unicode scalar,注意前面的 variation selector
キーポイント:
- デフォルト
.matchingSemantics(.graphemeCluster)Swift キャラクターと一致する .matchingSemantics(.unicodeScalar)Unicode スカラー レベルの一致- ほとんどのシナリオではデフォルトの動作を使用し、正確な制御が必要な場合にのみ切り替えます。
ローカルトレースバック制御
(21:45) 通常のバックトラック メカニズムは諸刃の剣です。グローバル バックトラッキングは検索やあいまい一致に適していますが、完全一致ではパフォーマンスの問題が発生する可能性があります。
let fieldSeparator = /\s{2,}|\t/
// 8 个空格时,匹配全部 8 个;后续失败时回溯到 7 个、6 个...
Localビルダーはローカル ルックバック スコープを作成し、一致が成功した後に未試行の代替を破棄します。
let fieldSeparator = Local { /\s{2,}|\t/ }
let transactionMatcher = Regex {
Capture { /CREDIT|DEBIT/ }
fieldSeparator // 匹配全部空白,后续失败也不回溯
TryCapture(field) { timestamp ~= $0 ? $0 : nil }
fieldSeparator
TryCapture(field) { details ~= $0 ? $0 : nil }
// ...
}
キーポイント:
Local { ... }原子非捕捉基に相当- フィールド区切り文字など、正確に指定されたトークンに適しています
- 検索スペースを制限し、マッチング効率を向上させます
詳細
TryCapture: マッチングでカスタム ロジックを実行する
(18:55)TryCaptureクロージャを使用して照合プロセスに参加し、実行時検証を実装できるようにします。
let transactionMatcher = Regex {
Capture { /CREDIT|DEBIT/ }
fieldSeparator
TryCapture(field) { timestamp ~= $0 ? $0 : nil } // 运行时验证 timestamp 正则
fieldSeparator
TryCapture(field) { details ~= $0 ? $0 : nil } // 运行时验证 details 正则
fieldSeparator
// ...
}
キーポイント:
TryCaptureクロージャが戻りますnil一致が失敗したことを示します- クロージャはマッチングに積極的に参加し、マッチングプロセス中にあらゆる検証を実行できます。
- 既存のランタイム ルールまたはカスタム解析ロジックの統合に適しています
再利用可能な正規表現コンポーネント
(27:05) 一般的なパターンをカプセル化します。RegexComponent、さまざまな場所で再利用されます。
struct MailmapLine: RegexComponent {
@RegexComponentBuilder
var regex: Regex<(Substring, Substring?, Substring)> {
ZeroOrMore(.horizontalWhitespace)
Optionally {
Capture(OneOrMore(.noneOf("<#")))
}
.repetitionBehavior(.reluctant)
ZeroOrMore(.horizontalWhitespace)
"<"
Capture(OneOrMore(.noneOf(">#")))
">"
ZeroOrMore(.horizontalWhitespace)
/#|\Z/
}
}
// 使用
let match = line.prefixMatch(of: MailmapLine())
キーポイント:
RegexComponentプロトコルにより、カスタム タイプを通常のコンポーネントとして使用できるようになります@RegexComponentBuilderプロパティ ラッパーによりビルダー構文が有効になります- コンポーネント内でリテラルとビルダーを混在させることができます
Embed Foundation パーサー
struct DatedMailmapLine: RegexComponent {
@RegexComponentBuilder
var regex: Regex<(Substring, Substring?, Substring, Date)> {
// ... 前面的邮件映射模式 ...
Capture(.iso8601.year().month().day()) // 直接产出 Date
ZeroOrMore(.horizontalWhitespace)
/#|\Z/
}
}
キーポイント:
.iso8601.year().month().day()財団の日付解析戦略ですか- キャプチャ タイプは直接です。
Date、その後手動で変換する必要はありません。 - 解析戦略に準拠する任意の型を Regex Builder に埋め込むことができます
重要ポイント
1.すべての文字列インデックス操作を Regex に置き換えます
使用アイテムをすべてチェックするString.index、Substring範囲操作、.components(separatedBy:)文字列が解析される場所。特に、ログの解析、CSV/TSV 処理、設定ファイルの読み取り、ユーザー入力の検証です。 Swift Regex の強力な型キャプチャにより、解析結果を直接利用できるようになり、中間変換が削減されます。
入り口:split(separator:)、prefixMatch(of:)、firstMatch(of:)、replacing(_:with:)
2. NS RegularExpression の代わりに Regex Builder を使用してください
今でもプロジェクトで使用されていますNSRegularExpression代わりに、Regex Builder に移行します。宣言構文がより読みやすくなり、強力な型のキャプチャが回避されます。NSTextCheckingResultインデックス作成操作は、デフォルトで Unicode によって正しく処理されます。シンプル モードから始めて、徐々に移行できます。
入り口:import RegexBuilder,Regex { ... }
3.カプセル化ビジネス固有の RegexComponent
プロジェクトで再利用される通常のパターンをカプセル化します。RegexComponent。たとえば、電子メールの検証、携帯電話番号の形式、注文番号のルール、ログのタイムスタンプなどです。コンポーネントを再利用すると一貫性が確保され、変更する場合は 1 か所だけ変更するだけで済みます。
入り口:struct EmailPattern: RegexComponent { ... }
4. Local を使用してパフォーマンス重視の正規表現を制御します
大量のテキストが処理されるシナリオ (ログ分析、データ クリーニング、リアルタイム フィルタリング) の場合は、正規表現に不必要なグローバル バックトラッキングがないかどうかを確認してください。フィールド区切り文字、固定形式トークン、その他の正確なパターンが使用されますLocal { ... }パッケージでは、検索スペースが制限されます。
入り口:Local { /pattern/ }
関連セッション
- Swift の新機能 — Regex と RegexBuilder の導入背景を含む、Swift 5.7 の新機能の概要
- Swift でのプロトコル インターフェイスの設計 - Swift 5.7 の一般的な改善、正規表現の強力な型キャプチャはこれらの言語機能に依存しています
- Swift 非同期アルゴリズムの紹介 — ストリーミング テキスト データを処理するために正規表現で使用できる非同期アルゴリズム ライブラリ
コメント
GitHub Issues · utterances