首页 > > > 10450 用 Unicode 分析韩文字词
韩文字是由初声、中声、终声三个部分组成的,例如 한 字的初声是 ㅎ,中声是 ㅏ,终声是 ㄴ。现代韩文有 19 种初声、21 种中声、28 种终声(见下表),理论上可以拼出 19 * 21 * 28 = 11,172 个字。不过,在这些字中,有许多结构复杂、面目狰狞的字(例如 쮎),实际中是用不到的。是收录所有可能的字,还是仅收录实际中能用到的字(下称「常用字」),就是韩文字符集面临的一个重要选择。

1.1 KS X 1001 字符集
KS X 1001 是 20 世纪常用的韩文字符集。它跟中国的 GB2312 字符集一样,分为 94 个区, 每个区有 94 个位,一共能编码 8,836 个字符。由于放不下全部 11,172 个可能的韩文字,该字符集只收录了 2,350 个常用字,放在了 16 ~ 40 区;此外,在 1 ~ 13 区放置了字母、数字、特殊符号等,42 ~ 93 区放置了汉字。完整的码表可以点这里下载:https://resources.oreilly.com/examples/9781565922242/raw/master/AppL/ksx1001.pdf。
在码位资源匮乏的年代,仅收录常用字无可厚非。不过,这种做法确实造成了诸多不便:
(相关问题:每一个Unicode里编码的谚文(韩文)字符都曾在历史上使用过吗? - 韩语 - 知乎)
KS X 1001 字符集还有两个比较有意思的特点:

1.2 Unicode
Unicode 的码位资源十分充裕,因此收录了全部 11,172 个韩文字,编码范围为 U+AC00 至 U+D7A3(带 U+ 前缀的编码都是 16 进制,下同)。这些韩文字的排列顺序如下:
这种有规律的排列方式,使得可以通过编程,方便地获取一个韩文字的部件,以及把部件组合成字。如果已知一个字的初声、中声、终声的编号分别为 x、y、z,那么这个字的编码就是 U+AC00 + (x * 21 + y) * 28 + z;如果已知一个字的 Unicode 编码 u,则可以按以下公式得到它的初声、中声、终声的编号:
举个例子:없 这个字的初声 ㅇ 编号为 11,中声 ㅓ 编号为 4,终声 ㅄ 编号为 18。于是这个字的 Unicode 编码就是 U+AC00 + (18 * 21 + 4) * 28 + 18 = U+C5C6。同样地,由 U+C5C6 也可以反推出各个部件的编号:
在这一节中,我将举两个例子,说明如何利用上面的公式,编程分析韩文的字词。为了输入和显示的方便,我将使用 JavaScript 语言。在 Chrome 浏览器中按 F12 键打开开发者工具窗口,选择 Console(控制台)标签页,就可以输入及执行程序了。
2.1 韩文与罗马字的相互转换
本例中采用的罗马字方案见文首的表。这并不是任何一种通行的罗马字方案。它的特点是转写辅音所用的字母与韩文字母基本一一对应,能够区分各种发音相同但拼写不同的终声,使得从罗马字转换成韩文时答案是唯一的。
编程时,首先要定义如下三个数组,表示每个初声、中声、终声对应的罗马字:
var initials = ['g', 'gg', 'n', 'd', 'dd', 'r', 'm', 'b', 'bb', 's', 'ss', '', 'j', 'jj', 'ch', 'k', 't', 'p', 'h'];
var medials = ['a', 'ae', 'ya', 'yae', 'eo', 'e', 'yeo', 'ye', 'o', 'wa', 'wae', 'oe', 'yo', 'u', 'wo', 'we', 'wi', 'yu', 'eu', 'eui', 'i'];
var finals = ['', 'g', 'gg', 'gs', 'n', 'nj', 'nh', 'd', 'l', 'lg', 'lm', 'lb', 'ls', 'lt', 'lp', 'lh', 'm', 'b', 'bs', 's', 'ss', 'ng', 'j', 'ch', 'k', 't', 'p', 'h'];
把韩文转换成罗马字的函数如下。为方便起见,可以输入一个韩文字符串,该函数会把其中的每一个韩文字转换成罗马字。
function han2rom(han) {
var rom = [];
for (var i = 0; i < han.length; i++) { // 处理每一个字
var u = han.charCodeAt(i) - 0xAC00; // 取得 Unicode 编码,减去起始值
var z = u % 28; // 终声编号
var y = Math.floor(u / 28) % 21; // 中声编号
var x = Math.floor(u / (28 * 21)); // 初声编号
rom.push(initials[x] + medials[y] + finals[z]);
// 查询各部分的罗马字,并连接起来
}
return rom.join(' '); // 用空格连接各个字的罗马字,并返回
}
把罗马字转换成韩文的函数如下。同样地,可以输入多个韩文字的罗马字,以空格分隔,函数会把每个字的罗马字转换成韩文。
function rom2han(rom) {
var s = rom.split(' '); // 按空格切分出每个字的罗马字
for (var i = 0; i < s.length; i++) { // 依次处理每个字
var matches = s[i].match(/([^aeiouwy]*)([aeiouwy]+)([^aeiouwy]*)/);
// 根据元音、辅音字母的区分,用正则表达式取出罗马字中的初声、中声、终声部分
var x = initials.indexOf(matches[1]); // 在数组中查询初声的编号
var y = medials.indexOf(matches[2]); // 中声编号
var z = finals.indexOf(matches[3]); // 终声编号
s[i] = String.fromCharCode(0xAC00 + (x * 21 + y) * 28 + z);
// 按公式计算整字的 Unicode 编码
}
return s.join('');
}
查询初声、中声、终声编号时使用了数组的 indexOf 方法,它需要遍历数组,效率并不高。若要追求效率,可以使用哈希表。
程序执行效果如下:

我编写的「汉字古今中外读音查询」安卓应用中,就有实现上述功能的代码,见 Orthography.java 中的 Korean 类(目前在第 581 行)。
2.2 将韩语动词或形容词变为「思密达」形
在此,我们想编写一个函数,输入一个动词或形容词的原型,输出它的「思密达」形。例如,输入「가다」,输出「갑니다」;输入「고맙다」,输出「고맙습니다」。
先复习一下「思密达」形的构成规则:
为了知道应该应用哪条规则,需要用程序获取词干最后一个字的终声。
变「思密达」形的函数如下:
function seubnida(s) {
var L = s.length; // 单词总长度
var u = s.charCodeAt(L - 2); // 词干最后一个字的 Unicode 编码
var z = (u - 0xAC00) % 28; // 词干最后一个字的终声编号
if (z == 0 || z == 8) { // 如果没有终声(编号为 0)或终声是ㄹ(编号为 8)
return s.substring(0, L - 2) + String.fromCharCode(u - z + 17) + '니다';
// 取单词的前 L-2 个字 将词干末字的终声改成ㅂ(编号为 17) 再加니다
}
else { // 如果终声是其它辅音
return s.substring(0, L - 1) + '습니다';
// 取词干 再加습니다
}
}
程序执行效果如下:

原文作者:;如有侵权,请联系管理员删除。