【问题标题】:Simple spell checking algorithm简单的拼写检查算法
【发布时间】:2011-10-18 10:36:15
【问题描述】:

我的任务是为一项作业创建一个简单的拼写检查器,但几乎没有给出任何指导,所以想知道是否有人可以帮助我。我不是在找人帮我做作业,但是任何关于算法的指导或帮助都会很棒!如果我要问的内容不在网站的范围内,那么我很抱歉,我会去别处看看。 :)

项目加载正确拼写的小写单词,然后需要根据两个标准提出拼写建议:

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

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

所以,只是为了确保我已经正确解释.. 我可能会加载单词 [hello, goodbye, wonderful, good, god],然后对(拼写错误的)单词“godd”的建议将是 [好,上帝]。

速度是我在这里的主要考虑因素,所以虽然我认为我知道完成这项工作的方法,但我真的不太确定它的效率如何。我正在考虑这样做的方式是创建一个map<string, vector<string>>,然后对于加载的每个正确拼写的单词,将正确拼写的工作添加为地图中的键,并将向量填充为所有可能的'那个词的错误排列。

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

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

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

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

【问题讨论】:

  • 有不少自然语言处理工具是开源的,可以作为使用算法的灵感来源。对于 C++ 作业,查看 Java 开源工具可能是一个不错的选择,以确保您只选择算法:例如,请参阅 languagetool.org

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


【解决方案1】:

解决问题的更简单的方法确实是预计算的地图[坏词] -> [建议]。

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

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

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

解决方案是逐步描述的,通常搜索速度应该在每个想法上不断提高,我尝试先用更简单的想法(在实施方面)来组织它们。只要您对结果感到满意,请随时停止。


0.初步

  • 实现 Levenshtein 距离算法
  • 按排序顺序存储字典(例如std::set,尽管排序后的std::dequestd::vector 会更好地提高性能)

关键点:

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

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


1.第一个暂定

让我们从简单的开始。

我们将实现线性扫描:对于每个单词,我们计算距离(短路),并列出迄今为止距离较小的单词。

它在小型字典上效果很好。


2.改进数据结构

levenshtein 距离至少等于长度之差。

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


3.前缀和修剪

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

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

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

假设您正在检查您的单词与cartoon 并在car 您意识到它不起作用(距离已经太长),那么任何以car 开头的单词也将不起作用,您可以跳过以car开头的单词。

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

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

“长”有多长取决于您的字典,您必须测量。我会从二分搜索开始。

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


4.前缀和重用

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

假设你有两个词:

  • 卡通
  • 洗车

您首先逐行计算cartoon 的矩阵。那么在读取carwash时需要确定公共前缀的长度(这里是car)并且可以保留矩阵的前4行(对应void,car) .

因此,当开始计算carwash 时,实际上是从w 开始迭代。

为此,只需使用在搜索开始时直接分配的数组,并使其足够大以容纳更大的引用(您应该知道字典中的最大长度是多少)。


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

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

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

【讨论】:

  • 嗯,作为Node{unsigned short prevIndex, bool end; unsigned short nextIndex[26];} 的向量实现的 Trie 可以在仅 3.5MB 的空间中保存 65536 个单词(适合 i7 的 L3 缓存!),并且会使拼写检查器成为一个相对简单的递归问题。我要玩那个
  • @MooingDuck:如果你维护一个“胖”迭代器,你就不需要prevIndex
  • 在递归中使用 prevIndex 可能比使用迭代器更容易。但你是对的,它不需要在那里。
  • 初始测试显示我的 Trie 是哈希表时间的两倍。 Hashtable 获取一个字符串,对其进行模糊测试,并查看哈希表中是否有任何模糊,根据它的模糊程度和多少进行排名。在我验证两者的准确性之前没有任何意义。
  • 很抱歉这么久才回复,但非常感谢,真的很有用。 :)
【解决方案2】:

你应该看看 Peter Norvig 关于如何编写拼写校正器的解释。

How to write a spelling corrector

这篇文章很好地解释了一切,例如,拼写检查器的 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 网站上找到您需要的内容。

【讨论】:

    【解决方案3】:

    对于拼写检查器,许多数据结构都会很有用,例如 BK-Tree。检查Damn Cool Algorithms, Part 1: BK-Trees我已经为相同的here完成了实现

    我的早期代码链接可能具有误导性,this one 是正确的拼写纠正器。

    【讨论】:

      【解决方案4】:

      在我的脑海中,您可以根据长度拆分您的建议,并构建一个树形结构,其中子级是较短父级的较长变体。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-07-20
        • 2011-10-30
        • 2012-03-04
        • 2017-08-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多