我不是技术专家,但一直在研究这个问题,因为我想确保波斯-阿拉伯双语 Discourse 论坛不会错过任何搜索查询。由于 Discourse 使用 PostgreSQL,因此规范化变得至关重要:用户可能使用波斯语字符进行搜索,而相同的单词以阿拉伯语字符存储——反之亦然。没有适当的规范化,搜索就会失败。
根据我的了解,使用 Unicode NFKC 规范化是一个不错的起点——它处理了许多兼容性问题,如连字、表示形式以及阿拉伯/波斯数字。
然而,对于波斯语和阿拉伯语文本,仅 NFKC 并不足够。它无法规范化几个关键的字符变体,这些变体在视觉和语义上是等效的,但在二进制级别上却不同。
下面,我将概述通过研究和探索得出的程序和见解。
总体设计策略
- 首先应用 Unicode NFKC 规范化,以处理连字、表示形式和数字统一。
- 然后按定义的顺序应用自定义字符映射(例如,在阿拉伯语 Ya 之前规范化 Hamza 变体)。
- 为存储与搜索设置不同的规范化策略:
- 对规范存储使用保守配置文件(保留 ZWNJ,避免语义偏移)。
- 对搜索使用宽松配置文件(忽略 ZWNJ,统一 Hamza 变体,规范化数字)。
- 所有映射都应通过数据库中的中央映射表或应用程序中的 Ruby hash 来配置。
规范化配置文件
保守(用于存储)
- 最小转换
- 应用 NFKC
- 将阿拉伯语 Kaf/Ya 规范化为波斯语等效项
- 删除变音符号
- 保留 ZWNJ
- 存储为
original_text+normalized_conservative
宽松(用于搜索)
- 积极匹配
- 应用所有保守规则
- 删除/忽略 ZWNJ
- 将 Hamza 变体规范化为基本字母
- 将所有数字转换为 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 | Hamza 形式 → Alef |
ؤ |
و |
U+0624 → U+0648 | Hamza Waw |
ئ |
ی |
U+0626 → U+06CC | Hamza 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 | 规范化数字 |
| Diacritics | — | U+064B–U+0652 | 删除所有 harakat |
| ZWJ | — | U+200D | 删除不可见的连接符 |
| Multiple spaces | Single space | — | 规范化间距 |
快速映射片段(用于 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 Hash 示例(用于 Discourse)
NORMALIZATION_MAP = {
"ك" => "ک",
"ي" => "ی",
"ى" => "ی",
"أ" => "ا",
"إ" => "ا",
"ٱ" => "ا",
"ؤ" => "و",
"ئ" => "ی",
"ة" => "ه",
"ۀ" => "هٔ",
"۴" => "4",
"٤" => "4",
"\u200C" => "", # ZWNJ
"\u200D" => "", # ZWJ
}
性能和实际注意事项
- 在应用程序层应用NFKC(例如,Ruby
unicode_normalize(:nfkc)) - 为保守和宽松配置文件使用单独的索引
- 除非明确配置,否则避免强制映射语义敏感字符(例如,Hamza、Taa Marbuta)
- 在实际论坛数据上运行A/B 测试,以衡量命中率和误报率
- 为每个映射提供理由和示例进行文档记录
- 为每个映射定义 Ruby 和 SQL 的单元测试
最终建议
- 使用 Unicode NFKC 作为基础
- 通过自定义映射层进行扩展
- 为存储和搜索维护双重配置文件
- 在应用程序和数据库层实现规范化
- 记录和测试每个映射
- 构建适当的索引(
GIN + to_tsvector)在规范化列上