简介
您要执行的操作称为Fuzzy search。让我引导你完成这个主题。
首先,设置 n-grams (Wikipedia) 的倒排索引 (Wikipedia)。也就是像"hello"这样的词拆分成,比如3-grams:
"$$h", "$he", "hel", "ell", "llo", "lo$", "o$$"
并有一个映射,将每个 n-gram 映射到包含它的单词列表:
"$$h" -> ["hello", "helloworld", "hi", "huhu", "hey"]
"$he" -> ["hello", "helloworld", "hey"]
...
"llo" -> ["hello", "helloworld", "llowaddup", "allo"]
...
您数据库中的所有单词现在都由它们的 n-gram 索引。这就是为什么它被称为 inverted 索引。
这个想法是,给定一个查询,计算该查询与数据库中的所有单词共有多少个 n-gram。这可以快速计算。之后,您可以使用它来跳过计算大量记录的昂贵编辑距离。这显着提高了速度。这是所有搜索引擎(或多或少)使用的标准方法。
让我首先通过完全匹配的例子来解释一般方法。之后我们稍微修改一下,就可以进行模糊匹配了。
完全匹配
在查询时,计算查询的 n-gram,获取列表并计算交集。
如果你得到"hello",你计算克数并得到:
"$$h", "$he", "hel", "ell", "llo", "lo$", "o$$"
您获取所有这些 n-gram 的所有列表:
List result;
foreach (String nGram) in (query.getNGrams()) {
List words = map.get(nGram);
result = result.intersect(words);
}
交集包含与这些字母完全匹配的所有单词,仅"hello"。
请注意,使用散列可以更快地计算完全匹配,例如 HashSet。
模糊匹配
不要与列表相交,而是合并它们。为了有效地合并,您应该使用任何k-way merge algorithm,不过它要求倒排索引中的单词列表事先排序,因此请确保在构造时对其进行排序。
您现在得到一个包含至少一个与查询相同的 n-gram 的所有单词的列表。
我们已经大大减少了可能的记录集。但我们可以做得更好。为每个单词维护与查询相同的 n-gram 数量。您可以轻松地在合并列表时做到这一点。
考虑以下阈值:
max(|x|, |y|) - 1 - (delta - 1) * n
x 是您的查询,y 是您要比较的候选词。 n 是您使用的 n-grams 的值,例如 3 如果 3-gram。 delta 是你允许多少错误的值。
如果计数低于该值,则直接知道编辑距离为
ED(x, y) > delta
所以你只需要考虑计数超过上述阈值的单词。仅针对您计算编辑距离的那些词ED(x, y)。
因此,我们极大地减少了可能的候选集,并且仅在少量记录上计算昂贵的编辑距离。
示例
假设您收到查询"hilari"。让我们使用3-grams。我们得到
"$$h", "$hi", "hil", "ila", "lar", "ari", "ri$", "i$$"
我们搜索倒排索引,合并具有这些共同词的单词列表,得到"hillary"、"haemophilia"、"solar"。连同这些词,我们计算了它们共有多少克:
"hillary" -> 4 ("$$h", "hi", "hil", "lar")
"haemophilia" -> 2 ("$$h", "hil")
"solar" -> 1 ("lar")
对照阈值检查每个条目。让delta 成为2。我们得到:
4 >= max(|"hilari"|, |"hillary"|) - 4 = 3
2 < max(|"hilari"|, |"haemophilia"|) - 4 = 6
1 < max(|"hilari"|, |"solar"|) - 4 = 2
只有"hillary" 高于阈值,丢弃其余的。计算所有剩余记录的编辑距离:
ED("hilari", "hillary") = 2
不超过delta = 2,所以我们接受。