ITPub博客

首页 > > > 10450 用 Unicode 分析韩文字词

10450 用 Unicode 分析韩文字词

作者:格伯纳 时间:2016-12-06 11:13:02 0 删除 编辑

一、韩文字符集简介

  韩文字是由初声、中声、终声三个部分组成的,例如 한 字的初声是 ㅎ,中声是 ㅏ,终声是 ㄴ。现代韩文有 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 区放置了汉字。完整的码表可以点这里下载:resources.oreilly.com/e。

  在码位资源匮乏的年代,仅收录常用字无可厚非。不过,这种做法确实造成了诸多不便:

    • 个别字在电脑或手机上输入的中间状态没有收录。比如收录了 쓩,却没有收录 쓔,这造成 쓩 字无法输入,或者虽然能输入,但中间状态 쓔 只能显示成 ㅆㅠ,让用户疑惑。
    • 在特殊场合下会用到的字没有收录。一个例子是,韩语语流音变非常丰富,在针对初学者的教材中,常常会标出单词经过语流音变后的发音。比如,单词 있다(有,在)的实际发音为 읻따,这个 읻 字就没有收录。
    • 由于常用字的分布没有规律,每个韩文字的组成部件与编码之间也就没有有规律的对应关系。若要分析字的结构或者单词的词形,则只能查表。

(相关问题:每一个Unicode里编码的谚文(韩文)字符都曾在历史上使用过吗? - 韩语 - 知乎)

  KS X 1001 字符集还有两个比较有意思的特点:

    • 在汉字部分,多音字的每个读音是分别编码的,比如「樂」字一共出现了四次,读音分别为 낙、락、악、요(相关问题:为什么 Unicode 中会存在「凉」和「凉」这样两个极其相像的字符?);
    • KS X 1001 字符集中韩文所在的区域(16 区开始),也正是 GB2312 字符集中「一级汉字」所在的区域。如果误把一篇韩文文档用中文编码打开,就会造成如下图所示的乱码,仿佛在讲一个「爸爸和舅舅喝着酒打起来了」的故事。

1.2 Unicode

  Unicode 的码位资源十分充裕,因此收录了全部 11,172 个韩文字,编码范围为 U+AC00 至 U+D7A3(带 U+ 前缀的编码都是 16 进制,下同)。这些韩文字的排列顺序如下:

    • 先选择第一个初声 ㄱ 和第一个中声 ㅏ 组合成 가,放在 U+AC00;
    • 然后依次遍历所有可能的终声,得到 각, 갂, 간, ..., 갛(共 27 字),放在 U+AC01 至 U+AC1B;
    • 然后换下一个中声 ㅐ,同样得到 개, 객, 갞, 갠, ..., 갷 等字,放在 U+AC1C 至 U+AC37;
    • 初声为 ㄱ 的字一共有 21 * 28 = 588 个,最后一个为 깋,放在 U+AE4B;
    • 然后再排初声为 ㄲ 的字,例如 까 U+AE4C,깍 U+AE4D,等等。

  这种有规律的排列方式,使得可以通过编程,方便地获取一个韩文字的部件,以及把部件组合成字。如果已知一个字的初声、中声、终声的编号分别为 x、y、z,那么这个字的编码就是 U+AC00 + (x * 21 + y) * 28 + z;如果已知一个字的 Unicode 编码 u,则可以按以下公式得到它的初声、中声、终声的编号:

    • 用 u 减去 U+AC00,再除以 28,得到商 v 和余数 z,z 就是终声编号;
    • 用 v 除以 21 得到商 x 和余数 y,y 就是中声编号,x 则是初声编号。

  举个例子:없 这个字的初声 ㅇ 编号为 11,中声 ㅓ 编号为 4,终声 ㅄ 编号为 18。于是这个字的 Unicode 编码就是 U+AC00 + (18 * 21 + 4) * 28 + 18 = U+C5C6。同样地,由 U+C5C6 也可以反推出各个部件的编号:

    • U+C5C6 - U+AC00 = 0x19C6 = 6598(0x 前缀表示 16 进制);
    • 6598 / 28 = 235 ... 18(18 为终声编号);
    • 235 / 21 = 11 ... 4(4 为中声编号,11 为初声编号)。

二、韩文字词分析两例

  在这一节中,我将举两个例子,说明如何利用上面的公式,编程分析韩文的字词。为了输入和显示的方便,我将使用 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) + '습니다';
           // 取词干                再加습니다
  }
}

  程序执行效果如下:

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

请登录后发表评论 登录
全部评论
管理员

注册时间:2018-03-30

  • 博文量
    2383
  • 访问量
    983938