在之前的一篇博文中我提到了使用百度五笔作为正版王码五笔的代替品。但是因为那里我所使用的词库是从网上所获得的,与王码原版的词库并不相同,所以在打字的时候就要改变自己的习惯,这显然是非常不方便的。今天,王码五笔又成功地让我的软件崩溃了两次之后,我决定彻底删除这款软件——一款声称是输入法,但其实和病毒的作用差不多的软件。为了能用其它的输入法达到和王码几乎相同的效果,我把目光放到了它的词库文件wmwb98qm.dat上。我感觉我能从里面提取出王码的词库信息,从而将其转移到其它的输入法上面。总而言之,我想制作一个完美的98版五笔词库,以供在其它软件中拿用。

王码词库编码格式

我利用WinHex软件反复观察wmwb98qm.dat的构造,并发现它其实是一个由定长条目组成的数据库文件。在文件开始处,是一个长256字节的文件头;随后即为数据条目,每个条目的长度为304字节。每个条目的头4个字节标明了条目的类型。据观察,一共有6种不同的条目:

  • 00000000:空条目
  • 01000000:单字

    其对应的编码为GBK,并保存于条目的第9-10字节。有的单字条目为偏旁部首,其并非GBK编码。在我的处理过程中,这些条目将被忽略。

  • 04000000:词组

    词组的GBK编码从条目的第48字节开始,直到遇到第一个00为止经结束。

  • 02000000:不在GBK编码内的汉字

    这些汉字是通过GB18030编码表示的,它们位于条目的第8-11字节。

  • 08000000:日期相关条目
  • 10000000:特殊符号

每个非空条目的4-7字节为其所对应的五笔编码。每个条目除了保存五笔编码所对应的汉字之外还会保存一些额外的信息(比如其拼音等),这些信息对我的目的而言并不重要,因此我没有尝试去解读它们。同时,我只提取了与单字和词组相关的条目。

条目范例

单字条目范例:

01000000 72717979 B5C40000 0A720E71 01790079 64006500 00006400
69000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000500 00000000 00000000 00000000 03060603 043C3E0F
07404610 0B444A16 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000

词组条目范例:

04000000 77726574 04000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 D3C5D6CA B7FECEF1
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
79006F75 00007A68 69000000 66007500 00007700 75000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000

生僻字条目范例:

02000000 61616162 8139EF31 12611261 12610062 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000

一级与二级简码

在王码的词库里未见一级与二级简码,因此我从网上搜集信息并制作了以下文件:

GFDSA HJKLM TREWQ YUIOP NBVCX

一地在要工上是中国同和的有人我主产不为这民了发以经

五于天末开下理事画现麦珀表珍万玉来求亚琛与击妻到互
十寺城某域直刊吉雷南才垢协零无坊增示赤过志坡雪支坶
三夺大厅左还百右面而故原历其克太辜砂矿达成破肆友龙
本票顶林模相查可柬贾枚析杉机构术样档杰枕札李根权楷
七革苦莆式牙划或苗贡攻区功共匹芳蒋东蘑芝艺节切芭药
睛睦非盯瞒步旧占卤贞睡睥肯具餐虔瞳叔虚瞎虑 眼眸此
量时晨果晓早昌蝇曙遇鉴蚯明蛤晚影暗晃显蛇电最归坚昆
号叶顺呆呀足虽吕喂员吃听另只兄唁咬吵嘛喧叫啊啸吧哟
车团因困轼四辊回田轴略斩男界罗罚较辘连思囝轨轻累
赋财央崧曲由则迥崭册败冈骨内见丹赠峭赃迪岂邮 峻幽
年等知条长处得各备身秩稀务答稳入冬秒秋乏乐秀委么每
后质拓打找看提扣押抽手折拥兵换搞拉泉扩近所报扫反指
且肚须采肛毡胆加舆觅用貌朋办胸肪胶膛脏边力服妥肥脂
全什估休代个介保佃仙八风佣从你信们偿伙伫亿他分公化
钱针然钉氏外旬名甸负儿勿角欠多久匀尔炙锭包迎争色锴
证计诚订试让刘训亩市放义衣认询方详就亦亮记享良充率
半斗头亲并着间问闸端道交前闪次六立冰普 闷疗妆痛北
光汗尖浦江小浊溃泗油少汽肖没沟济洋水渡党沁波当汉涨
精庄类床席业烛燥库灿庭粕粗府底广粒应炎迷断籽数序鹿
家守害宁赛寂审宫军宙客宾农空宛社实宵灾之官字安 它
那导居懒异收慢避惭届改怕尾恰懈心习尿屡忱已敢恨怪尼
卫际承阿陈耻阳职阵出降孤阴队陶及联孙耿辽也子限取陛
建寻姑杂既肃旭如姻妯九婢姐妗婚妨嫌录灵退恳好妇妈姆
马对参牺戏犋 台 观矣 能难物叉    予邓艰双牝
线结顷缚红引旨强细贯乡绵组给约纺弱纱继综纪极绍弘比

其中第一行是字母的顺序;第三行是一级简码;从第五第开始是一个矩阵,其竖向是第一码,横向为第二码,不存在的组合用空格代替。(注意在“累”字后有一空格)

结果

我写了以下程序来完成词库信息的提取与转换:


# open wangma data
with open('wmwb98qm.dat', 'rb') as infile:
    wmData = infile.read()

# the header is 256 bytes
# discard the header
wmData = wmData[256:]

# each record is 304 bytes
recordSize = 304

# make sure the file is valid
assert len(wmData) % recordSize == 0
numRecord = len(wmData)//recordSize

print('dictionary size: ', len(wmData))
print('number of record: ', numRecord)

def GetRecord(id):
    return wmData[id * recordSize : (id + 1) * recordSize]

def ParseKeyByte(keyBytes):
    key = keyBytes.decode('ascii').strip(' ')
    return key

def ParseRecord_SingleCharacter(record):
    key = ParseKeyByte(record[4:8])
    try:
        character = record[8:10].decode('gbk')
    except Exception as e:
        return None
    return key, character

def ParseRecord_Word(record):
    key = ParseKeyByte(record[4:8])

    # sequentially scan for the word
    # which starts at the 48th byte of the record
    indStart = 48
    indNow = indStart

    while indNow < recordSize and record[indNow] != 0:
        indNow += 1
    assert indNow < recordSize

    # extract the word
    word = record[indStart : indNow].decode('gbk')
    return key, word

def ParseRecord_ExtendedChrSet(record):
    key = ParseKeyByte(record[4:8])
    character = record[8:12].decode('gb18030')
    return key, character

def ParseRecord_None(record):
    return None

recordParserDict = {
    b'\x01\x00\x00\x00' : ParseRecord_SingleCharacter, # single character
    b'\x08\x00\x00\x00' : ParseRecord_None, # date
    b'\x10\x00\x00\x00' : ParseRecord_None, # special symbols
    b'\x04\x00\x00\x00' : ParseRecord_Word, # words/phrases
    b'\x02\x00\x00\x00' : ParseRecord_ExtendedChrSet, # characters not in GBK standard (encoded by GB18030)
    b'\x00\x00\x00\x00' : ParseRecord_None  # null record
}

result = []


for i in range(numRecord):
    record = GetRecord(i)
    recordType = record[0:4]
    recordFunc = recordParserDict[recordType]

    res = recordFunc(record)
    if res is not None:
        result.append(res)


import codecs
import re
with codecs.open('onetwo.txt', 'r', 'utf8') as infile:
    onetwoData = infile.readlines()

onetwoResult = []

letterSeq = re.sub(' ', '', onetwoData[0].lower().strip())
assert len(letterSeq) == 25

# append one-key characters
oneKeyLine = onetwoData[2]
for i in range(len(letterSeq)):
    onetwoResult.append((letterSeq[i], oneKeyLine[i]))

# append two-key characters
twoKeyGrid = [''] * len(letterSeq)
for i in range(len(letterSeq)):
    twoKeyGrid[i] = onetwoData[i + 4].strip('\n').strip('\r')
    assert len(twoKeyGrid[i]) == len(letterSeq)

for i in range(len(letterSeq)):
    for j in range(len(letterSeq)):
        key = letterSeq[i] + letterSeq[j]
        character = twoKeyGrid[i][j]
        if character != ' ':
            onetwoResult.append((key, character))

result = onetwoResult + result

# reorganize the result for baidu wubi
# when the same key combination appears multiple times in the database,
# the actual order is treated in reverse. so we have to manually reverse
# the occurance of those combinations
allKeys = [item[0] for item in result]
occurrence = dict()
for ind, item in enumerate(result):
    if item[0] in occurrence:
        occurrence[item[0]].append(ind)
    else:
        occurrence[item[0]] = [ind]

# filter out those that only appered once
toDelete = []
for key, val in occurrence.items():
    if len(val) <= 1:
        toDelete.append(key)
for key in toDelete:
    del occurrence[key]

# swap occurrence now
for key, val in occurrence.items():
    left = 0
    right = len(val) - 1

    while right > left and left != right:
        temp = result[val[left]]
        result[val[left]] = result[val[right]]
        result[val[right]] = temp
        left += 1
        right -= 1

# write results
with codecs.open('wb98perfection.txt', 'w', 'utf16') as outfile:
    for item in result:
        outfile.write('\t'.join(item) + '\r\n')

下载词库

这个词库可以导入百度五笔中使用。

注:本词库仅为算法设计所生成的附带产品,仅供学习及研究使用。若对版权有任何侵犯,请与我联系。我将立即删除。