同步: 最近刚来渠道, 方案将自己之前在其他地方发的文章转移 (同步) 过来.

首发日期2024-02-13, 以下为原文内容.


关于一个拼音输入法来说, 最重要也最基础的数据, 就是拼音和汉字的对应联系, 或者说拼音和汉字的对照表.

获取拼音数据有多种办法, 本文介绍其间的一种: 从 Unicode 规范 (Unihan 数据库) 获取拼音数据.

目录

  • 1 Unihan 数据库

  • 2 读取拼音数据

  • 3 处理拼音数据

  • 4 测验

  • 5 总结与展望

  • 附录 1 完好代码

1 Unihan 数据库

home.unicode.org/

Unicode 是一种编码方法 (encoding, 比如 utf-8), 也是一种字符集 (charset). Unicode 的方针是收集地球上一切言语运用的字符.

Unihan 数据库是一个关于汉字的数据库, 收录了几万个汉字, 包含多种数据.

www.unicode.org/charts/unih…

此处咱们只关怀拼音数据, 先把这个数据库下载下来:

wget "https://www.unicode.org/Public/UCD/latest/ucd/Unihan.zip"

然后解压:

unzip Unihan.zip

解压后能够看到几个文件:

> ls
Unihan_DictionaryIndices.txt   Unihan_OtherMappings.txt
Unihan_DictionaryLikeData.txt  Unihan_RadicalStrokeCounts.txt
Unihan_IRGSources.txt          Unihan_Readings.txt
Unihan_NumericValues.txt       Unihan_Variants.txt

其间 Unihan_Readings.txt 就包含了汉字的读音 (拼音) 数据.

2 读取拼音数据

翻开 Unihan_Readings.txt 文件 (这个文件有点大, 有 265481 行), 能够看到:

#
# Unihan_Readings.txt
# Date: 2023-07-15 00:00:00 GMT [KL]
# Unicode version: 15.1.0
#
# Unicode Character Database
#  2023 Unicode, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
# For documentation, see http://www.unicode.org/reports/tr38/
#
# This file contains data on the following fields from the Unihan database:
#	kCantonese
#	kDefinition
#	kHangul
#	kHanyuPinlu
#	kHanyuPinyin
#	kJapanese
#	kJapaneseKun
#	kJapaneseOn
#	kKorean
#	kMandarin
#	kSMSZD2003Readings
#	kTang
#	kTGHZ2013
#	kVietnamese
#	kXHC1983
#
# For details on the file format, see http://www.unicode.org/reports/tr38/
#
U 3400	kCantonese	jau1
U 3400	kDefinition	(same as U 4E18 丘) hillock or mound
U 3400	kJapanese	キュウ おか
U 3400	kMandarin	qi
U 3401	kDefinition	to lick; to taste, a mat, bamboo bark
U 3401	kHanyuPinyin	10019.020:tin
U 3401	kJapanese	テン
U 3401	kMandarin	tin
U 3402	kDefinition	(J) non-standard form of U 559C 喜, to like, love, enjoy; a joyful thing
U 3402	kJapanese	キ よろこぶ
U 3403	kCantonese	zim1
U 3404	kCantonese	kwaa1
U 3404	kJapanese	カ ケ
U 3404	kMandarin	ku
U 3405	kCantonese	ng5
U 3405	kDefinition	(an ancient form of U 4E94 五) five
U 3405	kJapanese	ゴ
U 3405	kMandarin	w

# 开头的行是注释. 其间 kMandarin 表明汉语拼音 (普通话), 比如:

U 3400	kMandarin	qi

这一行有 3 列, 之间以制表符 (tab) 分隔. 其间 U 3400 是字符编码, qi 是拼音.


弄清楚文件格局之后, 就能够写代码 (python) 来读取拼音数据了:

# 读取 Unihan_Readings.txt
def 读取数据(文件名):
    print(文件名)
    文本 = 读文件(文件名)
    o = []
    for i in 文本.split("n"):
        # 疏忽注释
        if i.startswith("#"):
            continue
        # 疏忽空行
        if len(i.strip()) < 1:
            continue
        # 处理 kMandarin
        p = i.split("	")  # 分隔符: 制表符 (tab)
        # 汉语拼音 (普通话)
        if p[1] == "kMandarin":
            汉字 = chr(int(p[0][2:], 16))
            o.append([汉字, p[2]])
    return o

读取整个文件, 依次处理每一行. 疏忽注释和空行, 然后只关怀 kMandarin 所在的行. 将 U 的格局转化成对应的字符.

3 处理拼音数据

上面取得的拼音是带有腔调的, 比如 qing. 一般拼音输入法是不带腔调的, 因此要进行转化处理.

# 计算拼音中出现的字符
def 计算拼音(数据):
    o = {}
    for i in 数据:
        for c in i[1]:
            if (ord(c) > ord("z")) or (ord(c) < ord("a")):
                o[c] = 1
    字符 = list(o.keys())
    字符.sort()
    print(字符)

咱们先来看看拼音中有哪些字符. 上面这段代码找出拼音中除 a ~ z 之外的一切字符.

def main():
    # 获取命令行参数
    输入 = sys.argv[1]
    输出 = sys.argv[2] if len(sys.argv) > 2 else None
    数据 = 读取数据(输入)
    if 输出 != None:
        成果 = 处理拼音(数据)
        写文件(输出, 成果)
    else:
        计算拼音(数据)
if __name__ == "__main__":
    main()

主函数这么写, 然后运转一下:

> python unihan_readings.py Unihan_Readings.txt
Unihan_Readings.txt
[' ', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'ḿ']

咱们就得到了一切需求特别处理的字符, 写一个对照表:

# 拼音字符对照表 (腔调)
拼音表 = {
    "": ("a", 1),
    "": ("a", 2),
    "": ("a", 3),
    "": ("a", 4),
    "": ("e", 1),
    "": ("e", 2),
    "": ("e", 3),
    "": ("e", 4),
    "": ("i", 1),
    "": ("i", 2),
    "": ("i", 3),
    "": ("i", 4),
    "": ("o", 1),
    "": ("o", 2),
    "": ("o", 3),
    "": ("o", 4),
    "": ("u", 1),
    "": ("u", 2),
    "": ("u", 3),
    "": ("u", 4),
    "": ("v", 0),
    "": ("v", 2),
    "": ("v", 3),
    "": ("v", 4),
    "": ("n", 2),
    "": ("n", 3),
    "": ("n", 4),
    "ḿ": ("m", 2),
}

然后就能够对单个拼音进行处理:

# 对拼音的每个字符进行处理
def 处理单个拼音(文本):
    腔调 = 0
    o = ""
    for c in 文本:
        if ord(c) > ord("z"):
            数据 = 拼音表[c]
            腔调 = 数据[1]
            o  = 数据[0]
        else:
            o  = c
    # 标示腔调
    if 腔调 > 0:
        o  = str(腔调)
    return o

查看拼音中的每个字符, 如果是特别字符, 就按照上表转化处理.

# 对原始拼音数据进行处理
def 处理拼音(数据):
    o = {}
    # 会集每个汉字的一切拼音
    for i in 数据:
        # 多音字
        拼音列表 = i[1].split(" ")
        for j in 拼音列表:
            拼音 = 处理单个拼音(j)
            汉字 = i[0]
            if o.get(汉字) == None:
                o[汉字] = [拼音]
            else:
                o[汉字].append(拼音)
    # 转化输出数据格局
    输出 = []
    列表 = list(o.keys())
    列表.sort()
    for i in 列表:
        行 = [i]   o[i]
        # DEBUG
        if len(o[i]) > 1:
            print(行)
        输出.append((" ").join(行))
    return ("n").join(输出)   "n"

对一切拼音进行处理, 然后保存成果.

4 测验

> python unihan_readings.py unihan/Unihan_Readings.txt pinyin.txt
unihan/Unihan_Readings.txt
['㪅', 'geng4', 'geng1']
['万', 'wan4', 'mo4']
['乾', 'qian2', 'gan1']
['俾', 'bi3', 'bi4']
['剋', 'kei1', 'ke4']
['剖', 'pou1', 'po3']
['剽', 'piao1', 'piao4']
['卜', 'bo', 'bu3']
['叚', 'xia2', 'jia3']
['嘸', 'fu3', 'wu3']
['噠', 'da1', 'da2']
['地', 'de', 'di4']
['堤', 'di1', 'ti2']
['差', 'cha4', 'cha1']
['帆', 'fan1', 'fan2']
['徵', 'zhi3', 'zheng1']
['擘', 'bai1', 'bo4']
['斗', 'dou4', 'dou3']
['杓', 'biao1', 'shao2']
['柏', 'bai3', 'bo2']
['氾', 'fan2', 'fan4']
['沈', 'shen3', 'chen2']
['沓', 'da2', 'ta4']
['甸', 'dian1', 'dian4']
['瞭', 'liao4', 'liao3']
['筽', 'ou1', 'wu2']
['繃', 'beng3', 'beng1']
['耙', 'ba4', 'pa2']
['舍', 'she3', 'she4']
['薄', 'bao2', 'bo2']
['袷', 'qia1', 'jia2']
['誰', 'shui2', 'shei2']
['諞', 'pian3', 'pian2']
['諷', 'feng3', 'feng4']
['識', 'shi2', 'shi4']
['讽', 'feng3', 'feng4']
['识', 'shi2', 'shi4']
['跌', 'die1', 'die2']
['蹣', 'pan2', 'man2']
['蹬', 'deng1', 'deng4']
['适', 'shi4', 'kuo4']
['都', 'dou1', 'du1']
['醱', 'fa1', 'po4']
['釐', 'xi1', 'li2']
['陂', 'bei1', 'pi2']
['隄', 'di1', 'ti2']
['隗', 'kui2', 'wei3']
['頗', 'po1', 'po3']
['髪', 'fa4', 'fa3']
['髮', 'fa4', 'fa3']
['麃', 'pao2', 'biao1']
['', 'ji4', 'ji1']

这里输出的是一切的多音字.

咱们查看一下成果文件 pinyin.txt:

> wc -l pinyin.txt
41419 pinyin.txt

一共有 41419 个汉字.

> head pinyin.txt
㐀 qiu1
㐁 tian4
㐄 kua4
㐅 wu3
㐆 yin3
㐌 yi2
㐖 xie2
㐜 chou2
㐡 nuo4
㐤 dan1

成果的前几行看起来是正确的.

> cat pinyin.txt | grep "[贫民小水滴]"
人 ren2
小 xiao3
水 shui3
滴 di1
穷 qiong2

随机抽查, 发现都获取了拼音.

(同步) 从 Unicode 规范提取拼音数据

5 总结与展望

Unihan 是 Unicode 规范中的数据库, 这些数据开源开放, 运用起来没有版权问题. 同时这个数据库的覆盖率较高, 收录了 4 万多个汉字的拼音.

可是这个数据的准确度和完好度是很值的怀疑的, 后续还需求手动批改.


  • 批改: Unihan_Readings.txt 数据中, 除了 kMandarin (普通话汉语拼音), 还有一种重要数据 kTGHZ2013, 这是 2013 年国家发布的 《通用规范汉字表》, 共收录汉字 8105 个.

    尽管这个拼音数据的覆盖规模小一些, 可是准确性和规范性都很高, 建议优先运用.

    拼音数据比照: kMandarin 有 41419 个汉字, kTGHZ2013 有 8105 个汉字. 相比 kMandarin, kTGHZ2013 新增 0 个汉字, 缺失 33314 个汉字. 其间 7304 个拼音相同, 801 个拼音不同. kTGHZ2013 增加了许多 多音字.


附录 1 完好代码

  • unihan_readings.py
#!/usr/bin/env python
# pmim-data/tool/unicode/unihan_readings.py
#
# > python --version
# Python 3.11.7
#
# 命令行格局 (举例):
# > python unihan_readings.py Unihan_Readings.txt pinyin.txt
import sys
import io
# 读取文本文件
def 读文件(文件名):
    with io.open(文件名, "r", encoding="utf-8") as f:
        return f.read()
# 写文本文件
def 写文件(文件名, 文本):
    with io.open(文件名, "w", encoding="utf-8") as f:
        f.write(文本)
# 读取 Unihan_Readings.txt
def 读取数据(文件名):
    print(文件名)
    文本 = 读文件(文件名)
    o = []
    for i in 文本.split("n"):
        # 疏忽注释
        if i.startswith("#"):
            continue
        # 疏忽空行
        if len(i.strip()) < 1:
            continue
        # 处理 kMandarin
        p = i.split("	")  # 分隔符: 制表符 (tab)
        # 汉语拼音 (普通话)
        if p[1] == "kMandarin":
            汉字 = chr(int(p[0][2:], 16))
            o.append([汉字, p[2]])
    return o
# 计算拼音中出现的字符
def 计算拼音(数据):
    o = {}
    for i in 数据:
        for c in i[1]:
            if (ord(c) > ord("z")) or (ord(c) < ord("a")):
                o[c] = 1
    字符 = list(o.keys())
    字符.sort()
    print(字符)
# 拼音字符对照表 (腔调)
拼音表 = {
    "": ("a", 1),
    "": ("a", 2),
    "": ("a", 3),
    "": ("a", 4),
    "": ("e", 1),
    "": ("e", 2),
    "": ("e", 3),
    "": ("e", 4),
    "": ("i", 1),
    "": ("i", 2),
    "": ("i", 3),
    "": ("i", 4),
    "": ("o", 1),
    "": ("o", 2),
    "": ("o", 3),
    "": ("o", 4),
    "": ("u", 1),
    "": ("u", 2),
    "": ("u", 3),
    "": ("u", 4),
    "": ("v", 0),
    "": ("v", 2),
    "": ("v", 3),
    "": ("v", 4),
    "": ("n", 2),
    "": ("n", 3),
    "": ("n", 4),
    "ḿ": ("m", 2),
}
# 对拼音的每个字符进行处理
def 处理单个拼音(文本):
    腔调 = 0
    o = ""
    for c in 文本:
        if ord(c) > ord("z"):
            数据 = 拼音表[c]
            腔调 = 数据[1]
            o  = 数据[0]
        else:
            o  = c
    # 标示腔调
    if 腔调 > 0:
        o  = str(腔调)
    return o
# 对原始拼音数据进行处理
def 处理拼音(数据):
    o = {}
    # 会集每个汉字的一切拼音
    for i in 数据:
        # 多音字
        拼音列表 = i[1].split(" ")
        for j in 拼音列表:
            拼音 = 处理单个拼音(j)
            汉字 = i[0]
            if o.get(汉字) == None:
                o[汉字] = [拼音]
            else:
                o[汉字].append(拼音)
    # 转化输出数据格局
    输出 = []
    列表 = list(o.keys())
    列表.sort()
    for i in 列表:
        行 = [i]   o[i]
        # DEBUG
        if len(o[i]) > 1:
            print(行)
        输出.append((" ").join(行))
    return ("n").join(输出)   "n"
def main():
    # 获取命令行参数
    输入 = sys.argv[1]
    输出 = sys.argv[2] if len(sys.argv) > 2 else None
    数据 = 读取数据(输入)
    if 输出 != None:
        成果 = 处理拼音(数据)
        写文件(输出, 成果)
    else:
        计算拼音(数据)
if __name__ == "__main__":
    main()

本文运用 CC-BY-SA 4.0 许可发布.