热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

完成拼写搜检器(spellcheck)

本文同时发在我的github博客上,迎接star在百度或许Google搜刮的时刻,有时会小手一抖,打错了一般字母,比方我们想搜刮apple,错打成了appel,但奇异的是,纵然我们

本文同时发在我的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 trie
this.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 for (let j = 0; 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 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的体式格局加快查询


推荐阅读
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Java程序设计第4周学习总结及注释应用的开发笔记
    本文由编程笔记#小编为大家整理,主要介绍了201521123087《Java程序设计》第4周学习总结相关的知识,包括注释的应用和使用类的注释与方法的注释进行注释的方法,并在Eclipse中查看。摘要内容大约为150字,提供了一定的参考价值。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
author-avatar
万源佳威5
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有