本文同时发在我的github博客上,欢迎star

在百度或者Google搜索的时候,有时会小手一抖,打错了个别字母,比如我们想搜索apple,错打成了appel,但神奇的是,即使我们敲下回车,搜索引擎也会自动搜索apple而不是appel,这是怎么实现的呢?本文就将从头实现一个JavaScript版的拼写检查器

基础理论

首先,我们要确定如何量化敲错单词的概率,我们将原本想打出的单词设为origin(O),错打的单词设为error(E)

贝叶斯定理我们可知:P(O|E)=P(O)*P(E|O)/P(E)

P(O|E)是我们需要的结果,也就是在打出错误单词E的情况下,原本想打的单词是O的概率

P(O)我们可以看作是O出现的概率,是先验概率,这个我们可以从大量的语料环境中获取

P(E|O)是原本想打单词O却打成了E的概率,这个可以用最短编辑距离模拟概率,比如原本想打的单词是apple,打成applee(最短编辑距离为1)的概率比appleee(最短编辑距离为2)自然要大

P(E)由于我们已知E,这个概念是固定的,而我们需要对比的是P(O1|E)、P(O2|E)...P(On|E)的概率,不需要精确的计算值,我们可以不用管它

具体实现

这部分的实现我参考了natural的代码,传送门

首先是构造函数:

function SpellCheck(priorList) {//to do triethis.priorList = priorList;this.priorHash = {};priorList.forEach(item => {!this.priorHash[item] && (this.priorHash[item] = 0);this.priorHash[item]++;});
}

priorList是语料库,在构造函数中我们对priorList中的单词进行了出现次数的统计,这也就可以被我们看作是先验概率P(O)

接下来是check函数,用来检测这个单词是否在语料库中出现

SpellCheck.prototype.check = function(word) {return this.priorList.indexOf(word) !== -1;
};

然后我们需要获取单词指定编辑距离内的所有可能性:

SpellCheck.prototype.getWordsByMaxDistance = function(wordList, maxDistance) {if (maxDistance === 0) {return wordList;}const listLength = wordList.length;wordList[listLength] = [];wordList[listLength - 1].forEach(item => {wordList[listLength].push(...this.getWordsByOneDistance(item));});return this.getWordsByMaxDistance(wordList, maxDistance - 1);
};
SpellCheck.prototype.getWordsByOneDistance = function(word) {const alphabet = "abcdefghijklmnopqrstuvwxyz";let result = [];for (let i = 0; i < word.length + 1; i++) {for (let j = 0; j < alphabet.length; j++) {//插入result.push(word.slice(0, i) + alphabet[j] + word.slice(i, word.length));//替换if (i > 0) {result.push(word.slice(0, i - 1) +alphabet[j] +word.slice(i, word.length));}}if (i > 0) {//删除result.push(word.slice(0, i - 1) + word.slice(i, word.length));//前后替换if (i < word.length) {result.push(word.slice(0, i - 1) +word[i] +word[i - 1] +word.slice(i + 1, word.length));}}}return result.filter((item, index) => {return index === result.indexOf(item);});
};

wordList是一个数组,它的第一项是只有原始单词的数组,第二项是存放距离原始单词编辑距离为1的单词数组,以此类推,直到到达了指定的最大编辑距离maxDistance

以下四种情况被视为编辑距离为1:

  • 插入一项,比如ab->abc
  • 替换一项,比如ab->ac
  • 删除一项,比如ab->a
  • 前后替换,比如ab->ba

获取了所有在指定编辑距离的单词候选集,再比较它们的先验概率:

SpellCheck.prototype.getCorrections = function(word, maxDistance = 1) {const candidate = this.getWordsByMaxDistance([[word]], maxDistance);let result = [];candidate.map(candidateList => {return candidateList.filter(item => this.check(item)).map(item => {return [item, this.priorHash[item]];}).sort((item1, item2) => item2[1] - item1[1]).map(item => item[0]);}).forEach(item => {result.push(...item);});return result.filter((item, index) => {return index === result.indexOf(item);});
};

最后得到的就是修正后的单词

我们来测试一下:

const spellCheck = new SpellCheck(["apple","apples","pear","grape","banana"
]);
spellCheck.getCorrectionsByCalcDistance("appel", 1); //[ 'apple' ]
spellCheck.getCorrectionsByCalcDistance("appel", 2); //[ 'apple', 'apples' ]

可以看到,在第一次测试的时候,我们指定了最大编辑距离为1,输入了错误的单词appel,最后返回修正项apple;而在第二次测试时,将最大编辑距离设为2,则返回了两个修正项

语料库较少的情况

上面的实现方法是先获取了单词所有指定编辑距离内的候选项,而在语料库单词较少的情况下,这种方法比较耗费时间,我们可以改成先获取语料库中符合指定最短编辑距离的单词

计算最短编辑距离是一种比较经典的动态规划(leetcode:72),dp即可。这里的计算最短编辑距离与leetcode的情况略有不同,需要多考虑一层临近字母左右替换的情况

leetcode情况下的状态转换方程:

  • dp[i][j]=0 i===0,j===0
  • dp[i][j]=j i===0,j>0
  • dp[i][j]=i j===0,i>0
  • min(dp[i-1][j-1]+cost,dp[i-1][j]+1,dp[i][j-1]+1) i,j>0

其中当word1[i-1]===word2[j-1]时,cost为0,否则为1

考虑临近字母左右替换的情况,则需要在i>1,j>1且word1[i - 2] === word2[j - 1]&&word1[i - 1] === word2[j - 2]为true的条件下,再作min(dp[i-1][j-1]+cost,dp[i-1][j]+1,dp[i][j-1]+1,dp[i-2][j-2]+1)

拿到语料库中符合指定最短编辑距离的单词在对先验概率作比较,代码如下:

SpellCheck.prototype.getCorrectionsByCalcDistance = function(word,maxDistance = 1
) {const candidate = [];for (let key in this.priorHash) {this.calcDistance(key, word) <= maxDistance && candidate.push(key);}return candidate.map(item => {return [item, this.priorHash[item]];}).sort((item1, item2) => item2[1] - item1[1]).map(item => item[0]);
};
SpellCheck.prototype.calcDistance = function(word1, word2) {const length1 = word1.length;const length2 = word2.length;let dp = [];for (let i = 0; i <= length1; i++) {dp[i] = [];for (let j = 0; j <= length2; j++) {if (i === 0) {dp[i][j] = j;continue;}if (j === 0) {dp[i][j] = i;continue;}const replaceCost =dp[i - 1][j - 1] + (word1[i - 1] === word2[j - 1] ? 0 : 1);let transposeCost = Infinity;if (i > 1 &&j > 1 &&word1[i - 2] === word2[j - 1] &&word1[i - 1] === word2[j - 2]) {transposeCost = dp[i - 2][i - 2] + 1;}dp[i][j] = Math.min(replaceCost,transposeCost,dp[i - 1][j] + 1,dp[i][j - 1] + 1);}}return dp[length1][length2];
};

最后

这份代码还有很多可以优化的地方,比如check函数使用的是indexOf判断单词是否在语料库中出现,我们可以改用单词查找树(Trie)或者hash的方式加速查询

实现拼写检查器(spell check)相关推荐

  1. 介绍 Java 平台的 Jazzy:一种新的拼写检查器 API

    计算机擅长执行快速搜索操作,可以根据给定的搜索词,对大量存储的信息快速进行搜索.但是,拼写检查应用程序所要求的搜索能力,不仅仅是正确的字符串匹配.在这篇文章中,我将介绍搜索算法的一些历史,包括语音匹配 ...

  2. Jazzy--一种新的拼写检查器API(Java平台)

    计算机擅长执行快速搜索操作,可以根据给定的搜索词,对大量存储的信息快速进行搜索.但是,拼写检查应用程序所要求的搜索能力,不仅仅是正确的字符串匹配. 在这篇文章中,我将介绍搜索算法的一些历史,包括语音匹 ...

  3. apache lucene_Apache Lucene拼写检查器的“您是不是要”功能

    apache lucene Google的"您是不是要"功能 在上一篇文章中对Lucene进行了介绍之后 ,现在是时候提高它并创建一个更复杂的应用程序了. 您肯定最熟悉Google ...

  4. Apache Lucene拼写检查器的“您是不是要”功能

    Google的"您是不是要"功能 在上一篇文章中对Lucene进行了介绍之后 ,现在是时候提高它,创建一个更复杂的应用程序了. 您肯定最熟悉Google的"您是不是要&q ...

  5. 怎样写一个拼写检查器-贝叶斯-python

    怎样写一个拼写检查器 Peter Norvig 翻译: Eric You XU 原版:http://norvig.com/spell-correct.html 翻译:http://blog.youxu ...

  6. 【ZT】怎样写一个拼写检查器

    这篇真的写的很棒,用心领会吧! 怎样写一个拼写检查器 Peter Norvig 翻译: Eric You XU 上个星期, 我的两个朋友 Dean 和 Bill 分别告诉我说他们对 Google 的快 ...

  7. 拼写检查器的编写[转]

    http://blog.youxu.info/spell-correct.html 怎样写一个拼写检查器 Peter Norvig 翻译: Eric You XU 上个星期, 我的两个朋友 Dean ...

  8. Python写一个简洁拼写检查器

    网上看到的一篇神文,利用的是朴素贝叶斯模型实现了一个简单的拼写检查器. 英文原文链接见这里,中文翻译如下 =============================================== ...

  9. 如何写一个拼写检查器-by Peter Norvig

    本文原著:Peter Norvig  中文翻译:徐宥 上个星期, 我的两个朋友 Dean 和 Bill 分别告诉我说他们对 Google 的快速高质量的拼写检查工具感到惊奇. 比如说在搜索的时候键入 ...

最新文章

  1. 这是我见过的最全的训练数据集,没有之一!
  2. 《Python Cookbook 3rd》笔记(4.15):顺序迭代合并后的排序迭代对象
  3. 云在天之南——我的七天七夜(率性苍山洱海)
  4. Jquery 查看DOM上绑定的事件列表
  5. android js 子线程,Android学习笔记:Android中的线程:MainThread 和 WorkerThread
  6. everything如何搜索文件内容?(这软件搜索文件可以,搜索文件内容不行)
  7. 计算机专业保研预推免面试记录
  8. spwm波正弦表生成程序,亲测实用有效!!!!!!速度摘取
  9. 015-JVM-使用javap查看class文件内容
  10. Jmeter的基础讲解
  11. uniapp本机号码一键登录
  12. Mac 安装 Icarus-Verilog 报错解决方案参考
  13. 软件测试登陆注册经典测试用例
  14. 学生认证免费使用pycharm专业版
  15. MDK自动生成版本号
  16. JAVA图形界面之JTable
  17. 腾讯云短信服务——获取验证码
  18. 项目管理中,工作汇报的目的有什么?
  19. 测绘程序设计 水准网实验 CSU
  20. 运动无线耳机什么牌子好?六款实用性不错的运动耳机推荐

热门文章

  1. aws azure_Microsoft Azure击败AWS的13种方式
  2. 【Apple】解决Testflight无法接入App Store
  3. 计算机鼠标怎么设置在哪里,电脑鼠标灵敏度怎么调,在哪里调?这份设置教程收好了非常简单(各系统通用)...
  4. DAY 5 | 自学前端第五天
  5. matlab 移位操作基础
  6. 苹果高通之基带芯片战争
  7. windows常用命令大全
  8. xp计算机远程在哪里,如何开启windows xp系统下的远程桌面
  9. java-net-php-python-ssm二手商品交易平台的设计与实现(2)计算机毕业设计程序
  10. springboot项目层次结构_SpringBoot的推荐项目目录结构