私は技術専門家ではありませんが、ペルシャ語とアラビア語のバイリンガルDiscourseフォーラムで検索クエリが漏れなく処理されるようにするために、この問題について調査してきました。DiscourseはPostgreSQLを使用しているため、正規化が不可欠です。ユーザーがペルシャ文字で検索しても、同じ単語がアラビア文字で保存されている場合やその逆の場合があります。適切な正規化なしでは、検索は失敗します。
私の調査と探求から学んだことに基づくと、Unicode NFKC正規化を使用することは確実な出発点です。これは、合字、表示形式、アラビア語/ペルシャ語の数字など、多くの互換性のあるケースを処理します。
しかし、ペルシャ語とアラビア語のテキストの場合、NFKCだけでは不十分です。これは、視覚的および意味的に同等でありながら、バイナリレベルで異なるいくつかの重要な文字バリアントを正規化しません。
以下に、私の調査と探求を通じて到達した手順と洞察を概説します。
全体的な設計戦略
- まずUnicode NFKC正規化を適用して、合字、表示形式、数字の統一を処理します。
- 次にカスタム文字マッピングを定義された順序で適用します(例:ハムザバリアントをアラビア語のヤより前に正規化)。
- ストレージと検索で正規化ポリシーを分離します。
- 正規のストレージには保守的なプロファイルを使用します(ZWNJを保持し、意味的なシフトを回避します)。
- 検索には寛容なプロファイルを使用します(ZWNJを無視し、ハムザバリアントを統一し、数字を正規化します)。
- すべてのマッピングは、データベースの中央マッピングテーブルまたはアプリケーションのRubyハッシュを介して設定可能である必要があります。
正規化プロファイル
保守的(ストレージ用)
- 最小限の変換
- NFKCを適用
- アラビア語のKaf/Yaをペルシャ語の同等物に正規化
- 発音記号を削除
- ZWNJを保持
original_text+normalized_conservativeとして保存
寛容(検索用)
- 積極的なマッチング
- すべての保守的なルールを適用
- ZWNJを削除/無視
- ハムザバリアントを基本文字に正規化
- すべての数字をASCIIに変換
- オプションでTaa Marbuta → Hehを統一
- クエリの前処理に使用
包括的なマッピングテーブル
| ソース | ターゲット | Unicode | 注釈 |
|---|---|---|---|
ك |
ک |
U+0643 → U+06A9 | アラビア語のKaf → ペルシャ語のKaf |
ي |
ی |
U+064A → U+06CC | アラビア語のYa → ペルシャ語のYa |
ى |
ی |
U+0649 → U+06CC | 末尾のYaバリアント |
أ, إ, ٱ |
ا |
Various → U+0627 | ハムザ形式 → Alef |
ؤ |
و |
U+0624 → U+0648 | ハムザWaw |
ئ |
ی |
U+0626 → U+06CC | ハムザYa |
ء |
— | U+0621 | 削除または保持(設定可能) |
ة |
ه |
U+0629 → U+0647 | Taa Marbuta → Heh(オプション) |
ۀ |
هٔ |
U+06C0 ↔ U+0647+U+0654 | 複合形式を正規化 |
ڭ |
گ |
U+06AD → U+06AF | 地域バリアント |
|
— | U+200C | ZWNJ:保守的では保持、寛容では削除 |
٤, ۴ |
4 |
U+0664, U+06F4 → ASCII | 数字を正規化 |
| 発音記号 | — | U+064B–U+0652 | すべてのharakatを削除 |
| ZWJ | — | U+200D | 可視化されない結合文字を削除 |
| 複数スペース | 単一スペース | — | スペーシングを正規化 |
高速マッピングスニペット(SQLまたはRuby用)
ك → ک
ي → ی
ى → ی
أ → ا
إ → ا
ؤ → و
ئ → ی
ة → ه
ۀ → هٔ
ٱ → ا
٤, ۴ → 4
ZWNJ (U+200C) → (寛容では削除)
Harakat (U+064B..U+0652) → 削除
ZWJ (U+200D) → 削除
PostgreSQLでの実装
text_normalization_mapテーブルを作成します。- パフォーマンスのために
regexp_replaceまたはTRANSLATEチェーンを使用します。 - オプションでPL/PythonまたはPL/v8でUnicodeサポートを実装します。
- 同じロジックを使用して、保存されたコンテンツと受信したクエリの両方を正規化します。
インデックス戦略
- 正規インデックス用に
normalized_conservativeを保存します。 normalize_persian_arabic(query, 'permissive')でクエリを正規化します。- 寛容な検索を使用する場合、インデックスは同じプロファイルと一致する必要があります。
- オプションで、クロス比較のために両方のバージョンを保存します。
Rubyハッシュの例(Discourse用)
NORMALIZATION_MAP = {
"ك" => "ک",
"ي" => "ی",
"ى" => "ی",
"أ" => "ا",
"إ" => "ا",
"ٱ" => "ا",
"ؤ" => "و",
"ئ" => "ی",
"ة" => "ه",
"ۀ" => "هٔ",
"۴" => "4",
"٤" => "4",
"\u200C" => "", # ZWNJ
"\u200D" => "", # ZWJ
}
パフォーマンスと実用的な注意点
- アプリケーションレイヤーでNFKCを適用します(例:Ruby
unicode_normalize(:nfkc))。 - 保守的プロファイルと寛容プロファイルで別々のインデックスを使用します。
- 特に設定されていない限り、意味的に重要な文字(例:ハムザ、Taa Marbuta)の強制的なマッピングは避けます。
- 実際のフォーラムデータでA/Bテストを実行して、ヒット率と偽陽性を測定します。
- 各マッピングを理由と例で文書化します。
- RubyとSQLの両方で各マッピングの単体テストを定義します。
最終推奨事項
- ベースとしてUnicode NFKCを使用します。
- カスタムマッピングレイヤーで拡張します。
- ストレージと検索でデュアルプロファイルを維持します。
- アプリケーションとデータベースレイヤーの両方に正規化を実装します。
- すべてのマッピングを文書化し、テストします。
- 適切なインデックス(
GIN + to_tsvector)を正規化された列に構築します。