创建一个“拼写检查”,检查具有合理运行时间的数据库

Posted

技术标签:

【中文标题】创建一个“拼写检查”,检查具有合理运行时间的数据库【英文标题】:Creating a "spell check" that checks against a database with a reasonable runtime 【发布时间】:2011-06-17 13:17:10 【问题描述】:

我不是在询问是否实施拼写检查算法本身。我有一个包含数十万条记录的数据库。我要做的是根据表中的某个列检查所有这些记录的用户输入,并返回具有一定汉明距离的任何匹配项(同样,这个问题不是关于确定汉明距离等)。当然,目的是创建一个“您的意思是”功能,用户在其中搜索名称,如果在数据库中没有找到直接匹配项,则返回可能匹配项的列表。

我正试图想出一种方法来尽可能在最合理的运行时间中完成所有这些检查。如何以最有效的方式对照所有这些记录检查用户的输入?

该功能目前已实现,但运行时非常慢。它现在的工作方式是将用户指定的一个(或多个)表中的所有记录加载到内存中,然后执行检查。

不管怎样,我使用 NHibernate 进行数据访问。

如果我能提供任何关于如何做到这一点或我的选择是什么的反馈,我将不胜感激。

【问题讨论】:

嗯,好问题!!! ;) 最简单的方法(假设您已经实现了它)是按照康拉德的建议进行操作,并在程序加载时加载数据库一次。至于多次访问数据库,确实需要查看您的建议背后的逻辑,以找出查询数据库的最佳方式。 如果将所有的DB记录加载到内存中,当DB发生变化时,是否需要管理同步数据,还是静态数据? @Drew:当我说“将用户指定的表(或表)中的所有记录加载到内存中”时,我的意思是当用户进行搜索但未找到结果时加载它们。无论如何,数据是相当静态的。 在这种情况下,我建议在内存中填充一个搜索结构并保留它以供多个查询使用。这是我所知道的在几十毫秒内完成你所说的事情的唯一方法。 【参考方案1】:

你标记为正确的答案..

Note: when i say dictionary.. in this post, i mean hash map .. map.. 
 basically i mean a python dictionary

另一种方法是通过创建单词的倒排索引来提高其性能。

因此,与其计算针对整个数据库的编辑距离,不如创建 26 个字典。每个字典都有一个键和一个字母。所以英语有 26 个字母.. 所以键是 "a","b".. "z"

所以假设你的数据库“apple”中有单词

所以在“a”字典中:添加单词“apple”

在“p”字典中:添加单词“apple”

在“l”字典中:添加单词“apple”

在“e”字典中:添加单词“apple”

所以,对字典中的所有单词执行此操作..

现在当输入拼写错误的单词时..

让我们说一下

你以“a”开头并检索“a”中的所有单词

然后你从“p”开始,找到“a”和“p”之间的单词交集

然后你从“l”开始,找到“a”、“p”和“l”之间的单词交集

你对所有字母都这样做。

最后你会得到一堆由字母“a”、“p”、“l”、“s”、“e”组成的单词

在下一步中,您将计算输入单词与上述步骤返回的一堆单词之间的编辑距离..从而大大减少您的运行时间..

现在可能会出现什么都没有返回的情况..

所以像“aklse”这样的东西..很有可能没有仅由这些字母组成的单词.. 在这种情况下,您将不得不开始将上述步骤反转到剩余单词数量有限的阶段。

所以有些东西以 *klse 开头(单词 k、l、s、e 之间的交集) num(wordsreturned) =k1

然后 a*lse( 单词之间的交集 a,l,s,e)... numwords = k2

等等.. 选择返回词数较多的那个。在这种情况下,真的没有一个答案。因为很多词可能有相同的编辑距离。你可以说如果editdistance大于“k”那么没有好的匹配...

在此之上构建了许多复杂的算法..

就像经过这么多步骤之后,使用统计推断(当输入是“aplse”时,这个词是“apple”的概率......等等)然后你就去机器学习了:)

【讨论】:

【参考方案2】:

计算 Levenshtein 距离并不像您想象的那么昂贵。 Norvig article 中的代码可以认为是伪代码,以帮助读者理解算法。一个更有效的实现(在我的例子中,在 20,000 个术语数据集上快大约 300 倍)是走trie。性能差异主要归因于无需分配数百万个字符串来进行字典查找,在 GC 中花费的时间更少,并且您还可以获得更好的引用局部性,从而减少 CPU 缓存未命中。使用这种方法,我可以在大约 2 毫秒内在我的 Web 服务器上进行查找。一个额外的好处是能够轻松返回以提供的字符串开头的所有结果。

缺点是创建 trie 很慢(可能需要一秒钟左右),因此如果源数据定期更改,那么您需要决定是重建整个事物还是应用增量。无论如何,一旦构建完成,您希望尽可能地重复使用该结构。

【讨论】:

见norvig.com/ngrams 的实现(实际上使用哈希表而不是trie,但想法是一样的)。 @Darius,该页面的代码与我之前提到的其他 Norvig 文章几乎相同,但它使用哈希表查找。就像我说的,伪代码是相似的,但是基于 trie 的方法的实现是完全不同的。 trie 不仅用于测试生成的术语是否存在。相反,它以一种无需生成编辑距离为 2 的所有术语的方式行走。 听起来您只是浏览了该代码并得到了错误的印象。 Norvig:“如果我们考虑所有的编辑,像‘acommodations’这样的词将产生 233,166 个候选词。但其中只有 11 个在词汇表中。因此,编辑通过预先计算词汇表中所有单词的所有前缀的集合来工作。然后它递归地调用editsR,将单词分成头部和尾部(代码中的hd和tl)并确保头部始终在前缀列表中。”它源自我发送给他的代码,又源自我的wry.me/~darius/hacks/spelling.tar.gz,它使用了 trie。 当我移植到 Python 时,在 dict 中表示前缀而不是在 trie 中表示前缀的效率更高。正如您所说,在C语言中,它很可能会走另一条路;但差异是一个很小的常数因素。 早上@Darius。我想也许我不够详细。我最初的实现是将 Norvig 的文章中的代码移植到 C# (edits1),生成替代项,然后查找它们。使用 trie 的好处是,您可以将这两个任务结合起来,最终对 trie 进行深度优先搜索,在行走时模拟替换/添加/等,并在达到最大编辑距离时缩短搜索。区别不仅仅是查找性能之间的区别。【参考方案3】:

正如 Darcara 所说,BK-Tree 是一个很好的首选。它们很容易实现。可以通过 Google 轻松找到几个免费的实现,但可以在此处找到更好的算法介绍:http://blog.notdot.net/2007/4/Damn-Cool-Algorithms-Part-1-BK-Trees。

不幸的是,计算 Levenshtein 距离的成本非常高,如果您使用带有大型字典的 BK-Tree,您会做很多事情。为了获得更好的性能,您可以考虑 Levenshtein Automata。实施起来有点困难,但效率也更高,它们可用于解决您的问题。同一位很棒的博主有详细信息:http://blog.notdot.net/2010/07/Damn-Cool-Algorithms-Levenshtein-Automata。这篇论文可能也很有趣:http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.16.652。

【讨论】:

不要以为我以前被称为很棒。 ;)【参考方案4】:

我猜Levenshtein distance 在这里比Hamming distance 更有用。

举个例子:我们使用单词example 并将自己的Levenshtein 距离限制为1。然后我们可以枚举所有可能存在的拼写错误:

1 次插入 (208) 一个例子 示例 示例 ... 例子 例子 例子 1 删除 (7) 示例 示例 示例 ... 示例 1 次替换 (182) 示例 示例 示例 ... 例子

您可以将每个拼写错误存储在数据库中,并将其链接到正确的拼写,example。这很有效,而且速度很快,但会创建一个庞大的数据库。

注意大多数拼写错误是如何通过对不同字符执行相同操作而发生的:

1 次插入 (8) ?示例 示例 例子 示例 示例?示例 示例文件 示例?e 例子? 1 删除 (7) 示例 示例 示例 例如 示例 示例 示例 1 次替换 (7) ?示例 示例 例如?mple 例如?ple 考试文件 示例?e 例子?

这看起来很容易管理。您可以为每个单词生成所有这些“提示”并将它们存储在数据库中。当用户输入一个单词时,生成所有的“提示”并查询数据库。

示例:用户输入exaple(注意缺少m)。

SELECT DISTINCT word
           FROM dictionary
          WHERE hint = '?exaple'
             OR hint = 'e?xaple'
             OR hint = 'ex?aple'
             OR hint = 'exa?ple'
             OR hint = 'exap?le'
             OR hint = 'exapl?e'
             OR hint = 'exaple?'
             OR hint = 'xaple'
             OR hint = 'eaple'
             OR hint = 'exple'
             OR hint = 'exale'
             OR hint = 'exape'
             OR hint = 'exapl'
             OR hint = '?xaple'
             OR hint = 'e?aple'
             OR hint = 'ex?ple'
             OR hint = 'exa?le'
             OR hint = 'exap?e'
             OR hint = 'exapl?'

exaple 有 1 个插入 == exa?ple == example 有 1 个替换

另见:How does the Google “Did you mean?” Algorithm work?

【讨论】:

+1 同意 Levenshtein 距离 - 不幸的是,Peter Norwig 文章没有解决内存问题 - 这是内存与处理速度的权衡 - 取决于将完整字典加载到内存中的大小可能不会甚至是可能的,但在数据库中保存所有拼写错误听起来也不对。 @BrokenGlass,使用 trie 可以减少大型数据集的内存占用。它还极大地提高了查找性能(在我的情况下快 200-300 倍。) 谢谢德鲁 - 我得调查一下 - 也赞成你的回答【参考方案5】:

您需要以不同于数据库的方式构建数据。在客户端上构建自定义搜索树,其中包含所需的所有字典数据。虽然如果字典非常大,内存可能会成为问题,但搜索本身会非常快。 O(nlogn) 如果我没记错的话。

看看BK-Trees

另外,不要使用汉明距离,而是考虑Levenshtein distance

【讨论】:

【参考方案6】:

它将用户指定表(或多个表)中的所有记录加载到内存中,然后执行检查

别这样

要么

在后端进行匹配匹配 并且只返回您需要的结果。

提前将记录缓存到内存中 在工作集上进行 需要时检查。

【讨论】:

以上是关于创建一个“拼写检查”,检查具有合理运行时间的数据库的主要内容,如果未能解决你的问题,请参考以下文章

如何在 google colab 中启用拼写检查器(colab 在 linux OS 上运行)?

vim拼写检查:忽略大写单词?

如何提高 C 程序中的拼写检查时间?

MindManager中的拼写检查功能如何用

桌面谷歌浏览器拼写检查是不是受到操作系统的影响?

内联禁用 Firefox 拼写检查?