输入法引擎的模型设计
最近在写《自表达代码》一书,该书第22章将会介绍一个Android平台上的日文输入法的开发过程。通过该开发过程展示如何在程序开发过程中保持代码的可读性、可扩展性和可变更性。
在写该部分之前,首先需要进行输入法引擎的设计。
下面是一个相对来说傻大笨粗的输入法引擎设计思路。虽然傻大笨粗,但是由于数据量并不大,数据算法次数并不多,所以该设计仍然是“可以接受的”。即处理时间上比较快、存储空间上占用不大。但是距离十分优秀的输入法还有很长的距离要走。
输入法的基本工作原理就是,输入一堆英文字符,然后利用英文字符到一个字典中去查找英文字符应该翻译成什么对应的自然语言文字。
这里提到的输入法引擎就按照这个思路来设计的。
首先,输入法的字典应该是一个二进制文件。
二进制文件中包含了这样三部分内容。
1. 文件头
表述了一些基本信息,比如:索引的尺寸,字典的尺寸等。 假定为100字节。
2. 索引
用来加快计算速度的。但是由于双字节语言(CJK)都是采用了长拼写的方式,并且还有词组的概念,所以这个索引会比较复杂。
还会涉及到索引的索引。具体后续会讨论。
3. 字典数据
用来存储具体的字典数据。
很显然,这样的数据结构是很容易被注入的,DOS下的一些病毒就是利用了文件分区表 的特点进行注入的。这个设计思路和文件分区表并无二致。
但是这里只是表明算法的基本思路,对于数据文件的保护可以通过其他的方式来进行,这里不做讨论。
为了能够更好的描述算法,从输入的角度开始,而不是从技术的角度开始。
以中文为例,输入zhuru,应该显示注入,诸如,侏儒三个候选汉字词组。
那么这个过程是怎么完成的呢?
首先,在索引中找zhu;然后,在zhu下面找ru;再然后在zhuru下找到词组。
这听起来很简单,不是吗?
那么在具体的技术中怎么实现的呢?
1. 索引中找zhu
把中文的声母 和韵母列成两张表格
声母:b p m f d t n l g k h j q x zh ch sh r z c s w y
韵母:a o e i u v ai ei ui ao uo ou ang eng ing ong an en in un vn iong...(还有很长)
分别依次对这些符号进行编号,都采用2位数字
比如:b = 01 p = 02..
a= 01 o =02..
那么zhu是多少呢? zh=15 u=05,那么zhu就是1505
ok 那么zhu的偏移量就是 (15(zh的id) x (韵母的个数) + 05(u的id)) x 4(4字节为单位)。
这个数字是干什么用的呢?100(文件头的尺寸) + 这个数字(zhu的偏移量)可以定位到文件中的一个位置。
假设韵母的个数是50吧(好算)
(15 x 50 + 05 ) x 4 = 755 x 4 = 3,020
再 + 100,就是3,120。
找到3120这个位置,读取4个字节,这4个字节是一个数字,这个数字是一个地址,这个数字表示了zhu开头的所有拼音组合的索引的开头地址。
比如它就是600000吧,那么我们定位到600,000这个位置,这里记录了zhua,zhuo,zhue,zhui,zhuu,zhuv等等所有的词组的索引。
然后,找到这个zhu开头的所有的索引之后,直接定位到其中的ru的位置。
怎么计算呢? 和刚才一样,r=18, u = 05,ru就是1,805。
然后ru的偏移量就是 (18 x 50 + 05) x 4 = 3,620。
那么定位到603,620这个位置,从这个位置也读取4个字节,它还是一个地址。比如就是5,000,000吧。
这个意思是说5,000,000这个位置开始是zhuru的词组,但是到底有几个呢?所以,这个位置上来不是词组,而是词组的一些简要信息,
1个字节,表示数据的个数。那么,这里应该是3。然后向后依次读取3 x (2 个汉字 + 3字节的词频) 。每2个字组成一个词组,返回回来。
这样算下来,一共需要的计算是
1. zhu的位置计算
2. ru的位置计算
3. zhuru的位置定位
4. zhuru的信息读取
5. 词组的读取
6. 词组的分离
这样几个步骤。可以达到秒杀了。
然后,看看文件的尺寸,
文件头100字节
索引的索引的尺寸 = 声母数 x 韵母数 x 4字节 = 21 x 50 x 4 = 4,200字节
索引的尺寸(按照只收录2字词组计算) = 索引的索引个数(第一个字) x 索引的索引个数(第二个字) x 4字节 = 4,410,000字节
然后,平均每种组合有3个词组(实际上会比这个略低一点,因为有很多拼音是不存在的,而且很多拼音是没有词组的)。
数据的尺寸 = 索引的个数(第一个字) x 索引的个数(第二个字) x 3(个词组) x (2个字/个词组 + 3字节/词频 ) x 2字节/字 + 索引的个数 x 索引的个数 x 1字节 =
(索引的个数 x 索引的个数) x ( 3 x (2 + 3)x 2 + 1) = 1050 x 1050 x 31 = 34,175,500 字节
累加 100 + 4,200 + 4,410,000 + 34,175,500 = 38,591,800
约36.8M字节。这还只是2个汉字的词组。这是无法容忍的。
上面的算法用下图可以表示出来。
所以,应当对词典进行瘦身,也应当对算法进行改进。
1. 由于每个拼音下面的汉字不会超过 256个,所以汉字可以用索引来代替,从而减少1个字节。
需要另外建立一张汉字与拼音对照表。
2. 重新认真的数一下韵母的个数
a o e i u v ai ei ui ao ou iu ie ve er an en in un vn ang eng ing ong uan uang ian iao iang iong
共30个。
另外,还有一个没有声母的情况。
3. 从索引中排除那些肯定不会存在的拼音
和v相拼的 bv pv mv fv dv tv gv kv hv jv qv xv zhv chv shv rv zv cv sv yv wv
和vn相拼的 bvn pvn mvn fvn dvn tvn gvn kvn hvn jvn qvn xvn zhvn chvn shvn rvn zvn cvn svn yvn wvn
和i相拼的gi ki hi wi
和ie相拼的fie gie kie hie zhie chie shie zie cie sie yie wie
和iu相拼的biu piu miu fiu giu kiu hiu zhiu chiu shiu riu ziu ciu siu yiu wiu
和in相拼的fin din tin gin kin hin zhin chin shin rin zin cin sin win
和ian相拼的fian gian kian hian zhian chian shian zian cian sian rian yian wian
和iao相拼的fiao giao kiao hiao zhiao chiao shiao ziao ciao siao yiao wiao
和iang相拼的biang piang miang fiang diang tiang giang kiang hiang zhiang chiang shiang ziang ciang siang yiang wiang
和iong相拼的biong piong miong fiong diong tiong niong liong giong kiong hiong zhiong chiong shiong ziong ciong siong yiong wiong
和un相拼的 bun pun mun fun nun
和uo相拼的buo puo muo fuo juo quo xuo yuo wuo
和uan相拼的 buan puan muan fuan wuan
和uang相拼的buang puang muang fuang duang tuang nuang luang ruang zuang cuang suang yuang wuang
和a相拼的ja qa xa ra
和ai相拼的fai jai qai xai rai yai
和an相拼的 jan qan xan
和ang相拼的 jang ang xang
和e相拼的je qe xe we
和ei相拼的dei zhei chei shei cei sei yei
和er相拼的ber per mer fer der ter ner ler ger ker her jer qer xer zher cher sher rer zer cer ser yer wer
和en相拼的den ten len jen qen xen
和eng相拼的jeng qeng xeng yeng weng
和o相拼的do to no lo go ko ho jo qo xo zho cho sho ro zo co so yo
和on相拼的don ton non lon gon kon hon jon qon xon zhon chon shon ron zon con son yon won
和ong相拼的jong qong xong yong wong
和ou相拼的bou jou qou xou you wou
(懒得数这有多少了,用程序计算了一下是292个。)
这需要牺牲算法时间为代价,因为不能仅仅通过offset来定位。(至于新算法也和老算法意思差不多,只是偏移形式重新规划要按照声母 + 韵母进行偏移量计算,而不是分开计算。)
22个声母 x 30 个韵母 - 292 = 368个组合
按照新思路重新计算一下空间
1. 100 字节的文件头
2. 368 x 4字节的索引的索引 = 1,472 字节
3. 368 x 368 x 4字节的索引 = 541,696字节
4. 368 x 368 x (3 x (1 + 3) x 2 + 1) = 3,385,600字节
累计为 3,928,868字节。容量已经缩减为3.7M左右了,是第一个方案的1/10左右。
之所以没有一上来就提这个3.7M的方案是为了能够先描述清楚思路。另外,对于这个方案还有瘦身的余地,但是思路已经描述清楚,就不再继续瘦身了。
好了,至此,一个简单的文字翻译引擎就设计完了。
只输入的时候xian是如何变成xian和xi'an两种组合的,则不在词典检索范畴来做,而是在输入端进行控制。
这个时候词频表的作用就体现了。