简单的拼写检查算法

时间:2011-10-18 10:36:16

标签: c++ algorithm language-agnostic nlp spell-checking

我的任务是为作业创建一个简单的拼写检查程序,但是旁边没有任何指导,所以想知道是否有人可以帮助我。我不是在跟别人为我做任务,但算法的任何方向或帮助都会很棒!如果我要求的不在网站的guildlines内,那么我很抱歉,我会在其他地方看看。 :)

项目正确加载拼写小写单词,然后根据两个标准制作拼写建议:

  • 一个字母差异(加上或减去以使单词与字典中的单词相同)。例如,'stack'将是'staick'的建议,'cool'将是'coo'的建议。

  • 一个字母替换。因此,例如,'bad'将是对'bod'的建议。

所以,只是为了确保我已经正确解释了......我可能会加载[你好,再见,太棒了,好,上帝]的字样然后对(错误拼写的)'godd'这个词的建议将是[好的,上帝]。

速度是我的主要考虑因素,所以当我认为我知道一种方法来完成这项工作时,我真的不太确定它的效率。我正在考虑这样做的方法是创建一个map<string, vector<string>>,然后为每个正确拼写的单词加载,将正确拼写的作品添加为地图中的一个键,并将向量填充为全部这个词的可能“错误”排列。

然后,当我想查找一个单词时,我会查看地图中的每个向量,看看该单词是否是正确拼写单词之一的排列。如果是,我会将密钥添加为拼写建议。

这似乎会占用内存的HEAPS,因为每个单词肯定会有成千上万的排列?如果我的正确拼写单词的初始词典很大,它似乎也会非常慢?

我在想,也许我可以通过查看与我正在看的单词相似的键来缩短时间。但话说回来,如果它们在某种程度上相似,那么它可能意味着关键将是一个建议意味着我不需要所有这些排列!

所以是的,我有点难过我应该看哪个方向。我真的很感激任何帮助,因为我真的不知道如何估计不同做事方式的速度(我们还没有在课堂上一直教这个。

4 个答案:

答案 0 :(得分:6)

解决问题的简单方法确实是预先计算好的地图[坏词] - &gt; [建议]。

问题在于,虽然删除一个字母会产生很少的“坏词”,但对于添加或替换,你有许多候选人。

所以我建议另一个解决方案;)

注意:您描述的编辑距离称为Levenshtein Distance

解决方案以增量步骤描述​​,通常搜索速度应该在每个想法中不断改进,并且我尝试首先使用更简单的想法(在实现方面)组织它们。每当您对结果感到满意时,请随时停下来。


0。初步

  • 实施Levenshtein距离算法
  • 将字典存储在已排序的序列中(例如std::set,但排序的std::dequestd::vector会更好地表现性能)

关键点:

  • Levenshtein距离计算使用数组,在每一步中,下一行仅使用前一行计算
  • 行中的最小距离始终优于(或等于)前一行中的最小值

后一个属性允许短路实现:如果您想将自己限制为2个错误(阈值),那么只要当前行的最小值优于2,您就可以放弃计算。一个简单的策略是将treshold + 1作为距离返回。


1。第一个暂定

让我们开始吧。

我们将实现线性扫描:对于每个单词,我们计算距离(短路),然后列出那些到目前为止距离较小的单词。

它适用于小词典。


2。改善数据结构

levenshtein距离至少等于长度的差异。

通过将夫妻(长度,单词)用作关键字而不仅仅是单词,您可以将搜索范围限制在[length - edit, length + edit]的范围内,并大大减少搜索空间。


3。前缀和修剪

为了改进这一点,我们可以说,当我们逐行构建距离矩阵时,一个世界被完全扫描(我们寻找的那个)但另一个(指示物)不是:我们只使用一个字母对于每一行。

这个非常重要的属性意味着对于共享相同初始序列(前缀)的两个指示对象,则矩阵的第一行将是相同的。

还记得我要求你存储字典吗?这意味着共享相同前缀的单词是相邻的。

假设您正在针对cartooncar检查您的单词,您发现它不起作用(距离已经太长),那么任何以car开头的单词都会赢得'无论如何,您都可以跳过单词,只要它们以car开头。

跳过本身可以线性完成或通过搜索完成(找到前缀高于car的第一个单词):

  • 线性效果最好,如果前缀很长(几个要跳过的话)
  • 二分搜索最适合短前缀(许多单词要跳过)

“长”的时间长短取决于你的字典,你必须衡量。我会继续二进制搜索。

注意:长度分区对前缀分区起作用,但它会修剪更多的搜索空间


4。前缀和重复使用

现在,我们也会尝试尽可能多地重复使用计算(而不仅仅是“它不起作用”的结果)

假设您有两个单词:

  • 卡通
  • 洗车

首先逐行计算cartoon的矩阵。然后,在阅读carwash时,您需要确定公共前缀的长度(此处为car),并且您可以保留矩阵的前4行(对应于void,c,{{ 1}},a)。

因此,当开始计算r时,您实际上开始在carwash进行迭代。

要做到这一点,只需使用在搜索开始时直接分配的数组,并使其足够大以容纳更大的引用(您应该知道字典中最大的长度)。


5。使用“更好”的数据结构

为了更轻松地使用前缀,您可以使用Trie或Patricia树来存储字典。然而,它不是STL数据结构,您需要对其进行扩充以在每个子树中存储存储的字长度范围,因此您必须自己实现。它并不像看起来那么容易,因为存在可能会破坏局部性的内存爆炸问题。

这是最后的选择。实施成本很高。

答案 1 :(得分:4)

你应该看一下Peter Norvig关于如何编写拼写纠错器的解释。

How to write a spelling corrector

本文很好地解释了EveryThing,作为一个例子,拼写检查器的python代码如下所示:

import re, collections

def words(text): return re.findall('[a-z]+', text.lower()) 

def train(features):
    model = collections.defaultdict(lambda: 1)
    for f in features:
        model[f] += 1
    return model

NWORDS = train(words(file('big.txt').read()))

alphabet = 'abcdefghijklmnopqrstuvwxyz'

def edits1(word):
   splits     = [(word[:i], word[i:]) for i in range(len(word) + 1)]
   deletes    = [a + b[1:] for a, b in splits if b]
   transposes = [a + b[1] + b[0] + b[2:] for a, b in splits if len(b)>1]
   replaces   = [a + c + b[1:] for a, b in splits for c in alphabet if b]
   inserts    = [a + c + b     for a, b in splits for c in alphabet]
   return set(deletes + transposes + replaces + inserts)

def known_edits2(word):
    return set(e2 for e1 in edits1(word) for e2 in edits1(e1) if e2 in NWORDS)

def known(words): return set(w for w in words if w in NWORDS)

def correct(word):
    candidates = known([word]) or known(edits1(word)) or known_edits2(word) or [word]
    return max(candidates, key=NWORDS.get)

希望您能在Peter Norvig网站上找到您需要的东西。

答案 2 :(得分:2)

对于拼写检查,许多数据结构对BK-Tree有用。检查Damn Cool Algorithms, Part 1: BK-Trees我已完成相同here

的实施

我的早期代码链接可能会产生误导,this one对于拼写纠错器是正确的。

答案 3 :(得分:0)

离开我的头顶,你可以根据长度分割你的建议并建立一个树结构,其中孩子是较短父母的较长变体。

应该很快,但我不确定代码本身,我不是很精通c ++