重建拼写检查器

2021-01-15 21:36:19

几年前,我对“周末项目”有一个有趣的主意:纯Ruby拼写检查器。 Ruby是我选择的语言,对于CI环境,无依赖性拼写检查器似乎是一个有用的小工具:例如,无需安装任何第三方软件即可检查注释/文档的拼写。实际上,我可以在有限的范围内(只有英语,只能发现拼写错误的单词而不需要修复,仅限字典)退出项目,仅列出已知单词,但事实并非如此。

那时,我决定制作一个中等程度的通用工具,至少能够使用多种语言。幸运的是(或者,我相信!),有许多已经存在并且免费提供的拼写检查字典,这些字典作为LibreOffice和Firefox扩展发布。所有这些字典均采用Hunspell工具/库定义的格式,该库是一个开放源代码库,用于Libre / OpenOffice,Mozilla产品(Firefox,Thunderbird)以及Google Chrome / Chromium,macOS,几种Adobe产品,等等。

字典看起来很容易重用带有某些(看起来“微不足道”)元数据的文本文件,并且整个“使用纯Ruby拼写检查器的Hunspell字典”项目在前几个周末仍然感觉像“周末”。 。逐渐发现了多语言逐字拼写检查的水下复杂性。最终,我从项目中分心了,放弃了它,但是我仍然对看似简单,实际上头脑混乱,非常复杂的Hunspell着迷,该软件是每个人每天都在使用且几乎没有注意到的软件。

深入研究,理解和解释的想法在我心中兴起,并困扰了我一段时间。如果不用自己的话来复述某事,还有什么更好的理解方法呢?经过几次懒惰且进展不大的尝试,编写了类似Hunspell的东西(在Ruby中两次,在Rust中一次,在Python中一次),最终,在2020年2月,我安心解决的任务是: Hunspell变成了高级语言,带有很多注释。到2020年12月,我通过Spylls项目的第一个版本实现了这一目标:将Hunspell的核心算法移植到现代,文档完善,结构良好的Python中。

现在,我想分享一些我在旅途中发现的见解:关于一般的拼写检查,尤其是关于Hunspell的拼写检查。

什么是Hunspell,为何如此重要,以及为何尝试“解释”它(当前文章)

查找(检查单词是否正确)的工作方式,以及为什么查找可能比“仅在已知单词列表中查找”复杂得多

建议(建议对不正确单词的修正)如何工作,以及评估其质量的难度

仔细研究Hunspell的字典格式。它是世界上使用最广泛的开放词典格式,我们将看到它可能包含的语言和算法信息,以及现有词典中实际使用的哪一部分

关于逐字拼写检查器问题的概观的总结思想以及Hunspell的解决方法

注意:关于Hunspell的起源和历史的信息大部分是我的猜测,部分地方信息来源不完整。

Hunspell()最初是匈牙利语的拼写检查工具,在2002年替代了以前存在的aspell / ispell / myspell(我猜呢?)。它是由LászlóNémeth创建的,它需要支持具有复杂的后缀/前缀规则和单词复合功能的语言(例如匈牙利语)。 Hunspell的设计似乎证明了自己的灵活性,足以支持世界上大多数语言,并且在短短几年内,它成为了世界上使用最多的拼写检查器。即使您从未听说过这个名称,您也很有可能使用了它:Hunspell是Chrome和Firefox,Libre / OpenOffice,Adobe产品和macOS(不是详尽列表)中的默认拼写检查引擎。 Hunspell格式的字典适用于几乎所有活跃使用的语言,对于这些语言而言,逐字拼写检查的概念有意义。1。

目前,Hunspell是在GitHub上维护的(repo只有大约1千颗星,您相信吗?)。如果您要权衡未解决的问题和PR的数量以及最新的提交时间表,似乎维护起来并不容易:在撰写本文时(2021年1月),对master的最后一次提交是2020年5月,最新版本是2018年12月的1.7。Hunspell的代码库主要是“老式” C ++。它正在缓慢地现代化,几乎没有评论。成千上万的两个分支if分别处理非Unicode和Unicode文本。还尝试用现代C ++从头重写Hunspell,该C ++在某些时候是由hunspell GitHub组织开发的。现在,它是独立的,称为nuspell(并且虽然尚未支持所有Hunspell功能,但已经“实现”了4.2.0版)。

显然,除了Hunspell之外,还有开源的拼写检查器。 GNU aspell(曾经被Hunspell取代,但在英语建议质量上仍然保持着自己的地位),以较早的名字命名;但是还有一些新颖的方法,例如SymSpell,声称“快一百万倍”,或者基于ML的JamSpell,声称更加准确。

然而,使Hunspell脱颖而出的是其对全球语言的报道。这不是理想的方法,但是要立即使用的字典数量以及处理典型问题和极端情况的经验(已编码到代码库中)很难被击败。

正如我上面已经说过的,Spylls项目的目标是创建一个说明性的重写:例如,“重新讲述” Hunspell如何以易于遵循和使用的方式工作。

众所周知,这种复杂性以及用于开发该项目的大量人力工作很难遵循Hunspell的代码库并无法完全掌握。

我选择的方式当然不是唯一的一种可能。我可以通读原始代码,然后撰写一系列有关其工作原理的文章(或者更确切地说,是一本书?)。我可以彻底评论并重新发布原始源代码。但是我觉得重新实现是理解算法的内容和原因的唯一方法(至少对于不是Hunspell核心开发人员的人而言);而且以高级语言实现的这种方法将允许您专注于单词和与语言相关的算法,而不是内存管理或与Unicode对抗。

请注意,Hunspell还有一些“实用”的端口可以移植到其他语言中(以便在不希望C ++依赖的环境中使用它),例如C#中的WeCantSpell.Hunspell和JS中的nspell(非常不完整)。前面提到的nuspell也可以看作是“端口”(从旧版C ++到现代的)。

我选择的语言是Ruby。这也是我尝试将Hunspell移植到的第一语言。如果我的目标只是一个“实用”的库,我很乐意继续使用Ruby。但是,当我确定目标是让广大读者可以访问Hunspell算法的知识时,我知道Ruby不是最好的选择:语言声誉(有点深奥,大多是针对网络的)会使我的项目免于负担显;我偏爱的编码风格(OO和功能的混合,带有许多小的不可变域对象和流畅的迭代器链),虽然使我非常有效,但会使其他语言用户无法访问该代码。

我需要的是一种高级语言,并尽可能减少样板。尽可能成为主流;尽可能容易地进行实验并制作原型。在这里无需过多讨论,Python和现代JavaScript似乎是最合适的选择,老实说,Python离我的灵魂更近了。所以,我们到了!

代码风格主要是强制性的(因为它与Hunspell的结构相对应),带有大量但结构清晰的方法以及少量的类/对象(大多数是“整个算法作为类”或几乎是-被动的“结构” –或在Python中为数据类)。我试图限制自己使用复杂的特定于Python的功能(例如functools或itertools),但是对“列表理解”(因为它们很易读且Pythonic)和生成器(惰性列表)使用得体。总的来说,我希望代码是好的Python,但又不要太聪明。我是否成功,取决于您的决定。

目前,Spylls在14个文件中具有约1.5k行的库代码。它符合(有些保留)所有Hunspell的集成测试。这些测试看起来像一组文件,每个文件包括“测试词典+哪些单词应被认为是好单词,哪些单词应被认为是不好的单词,应该建议什么来代替这些不好的单词”,并且有127种这样的设置可以通过。代码中有2000条注释行,详细解释了算法的每个细节,并在Spylls文档站点上进行了呈现。请注意,除了每个类和方法开头的文档字符串之外,代码中还包含内联注释,这就是文档站点使用具有内联“显示代码”功能的自定义主题的原因。

在下一个系列中:对Hunspell的“查找”和“建议”概念的介绍;并进一步深入查找。

逐字拼写检查对于中文和日文等汉字语言没有多大意义;对于单词之间没有空格分隔的语言(例如老挝语或泰语),这也存在问题。 ↩

分享: