实现拼写检查器(spell check)
本文同时发在我的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)相关推荐
- 介绍 Java 平台的 Jazzy:一种新的拼写检查器 API
计算机擅长执行快速搜索操作,可以根据给定的搜索词,对大量存储的信息快速进行搜索.但是,拼写检查应用程序所要求的搜索能力,不仅仅是正确的字符串匹配.在这篇文章中,我将介绍搜索算法的一些历史,包括语音匹配 ...
- Jazzy--一种新的拼写检查器API(Java平台)
计算机擅长执行快速搜索操作,可以根据给定的搜索词,对大量存储的信息快速进行搜索.但是,拼写检查应用程序所要求的搜索能力,不仅仅是正确的字符串匹配. 在这篇文章中,我将介绍搜索算法的一些历史,包括语音匹 ...
- apache lucene_Apache Lucene拼写检查器的“您是不是要”功能
apache lucene Google的"您是不是要"功能 在上一篇文章中对Lucene进行了介绍之后 ,现在是时候提高它并创建一个更复杂的应用程序了. 您肯定最熟悉Google ...
- Apache Lucene拼写检查器的“您是不是要”功能
Google的"您是不是要"功能 在上一篇文章中对Lucene进行了介绍之后 ,现在是时候提高它,创建一个更复杂的应用程序了. 您肯定最熟悉Google的"您是不是要&q ...
- 怎样写一个拼写检查器-贝叶斯-python
怎样写一个拼写检查器 Peter Norvig 翻译: Eric You XU 原版:http://norvig.com/spell-correct.html 翻译:http://blog.youxu ...
- 【ZT】怎样写一个拼写检查器
这篇真的写的很棒,用心领会吧! 怎样写一个拼写检查器 Peter Norvig 翻译: Eric You XU 上个星期, 我的两个朋友 Dean 和 Bill 分别告诉我说他们对 Google 的快 ...
- 拼写检查器的编写[转]
http://blog.youxu.info/spell-correct.html 怎样写一个拼写检查器 Peter Norvig 翻译: Eric You XU 上个星期, 我的两个朋友 Dean ...
- Python写一个简洁拼写检查器
网上看到的一篇神文,利用的是朴素贝叶斯模型实现了一个简单的拼写检查器. 英文原文链接见这里,中文翻译如下 =============================================== ...
- 如何写一个拼写检查器-by Peter Norvig
本文原著:Peter Norvig 中文翻译:徐宥 上个星期, 我的两个朋友 Dean 和 Bill 分别告诉我说他们对 Google 的快速高质量的拼写检查工具感到惊奇. 比如说在搜索的时候键入 ...
最新文章
- 这是我见过的最全的训练数据集,没有之一!
- 《Python Cookbook 3rd》笔记(4.15):顺序迭代合并后的排序迭代对象
- 云在天之南——我的七天七夜(率性苍山洱海)
- Jquery 查看DOM上绑定的事件列表
- android js 子线程,Android学习笔记:Android中的线程:MainThread 和 WorkerThread
- everything如何搜索文件内容?(这软件搜索文件可以,搜索文件内容不行)
- 计算机专业保研预推免面试记录
- spwm波正弦表生成程序,亲测实用有效!!!!!!速度摘取
- 015-JVM-使用javap查看class文件内容
- Jmeter的基础讲解
- uniapp本机号码一键登录
- Mac 安装 Icarus-Verilog 报错解决方案参考
- 软件测试登陆注册经典测试用例
- 学生认证免费使用pycharm专业版
- MDK自动生成版本号
- JAVA图形界面之JTable
- 腾讯云短信服务——获取验证码
- 项目管理中,工作汇报的目的有什么?
- 测绘程序设计 水准网实验 CSU
- 运动无线耳机什么牌子好?六款实用性不错的运动耳机推荐
热门文章
- aws azure_Microsoft Azure击败AWS的13种方式
- 【Apple】解决Testflight无法接入App Store
- 计算机鼠标怎么设置在哪里,电脑鼠标灵敏度怎么调,在哪里调?这份设置教程收好了非常简单(各系统通用)...
- DAY 5 | 自学前端第五天
- matlab 移位操作基础
- 苹果高通之基带芯片战争
- windows常用命令大全
- xp计算机远程在哪里,如何开启windows xp系统下的远程桌面
- java-net-php-python-ssm二手商品交易平台的设计与实现(2)计算机毕业设计程序
- springboot项目层次结构_SpringBoot的推荐项目目录结构