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 字符集還有兩個比較有意思的特點:
- 在漢字部分,多音字的每個讀音是分別編碼的,比如「樂」字一共出現了四次,讀音分別為 ?、?、?、?(相關問題:為什麼 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];nvar medials = [a, ae, ya, yae, eo, e, yeo, ye, o, wa, wae, oe, yo, u, wo, we, wi, yu, eu, eui, i];nvar 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];n
把韓文轉換成羅馬字的函數如下。為方便起見,可以輸入一個韓文字元串,該函數會把其中的每一個韓文字轉換成羅馬字。
function han2rom(han) {n var rom = [];n for (var i = 0; i < han.length; i++) { // 處理每一個字n var u = han.charCodeAt(i) - 0xAC00; // 取得 Unicode 編碼,減去起始值n var z = u % 28; // 終聲編號n var y = Math.floor(u / 28) % 21; // 中聲編號n var x = Math.floor(u / (28 * 21)); // 初聲編號n rom.push(initials[x] + medials[y] + finals[z]);n // 查詢各部分的羅馬字,並連接起來n }n return rom.join( ); // 用空格連接各個字的羅馬字,並返回n}n
把羅馬字轉換成韓文的函數如下。同樣地,可以輸入多個韓文字的羅馬字,以空格分隔,函數會把每個字的羅馬字轉換成韓文。
function rom2han(rom) {n var s = rom.split( ); // 按空格切分出每個字的羅馬字n for (var i = 0; i < s.length; i++) { // 依次處理每個字n var matches = s[i].match(/([^aeiouwy]*)([aeiouwy]+)([^aeiouwy]*)/);n // 根據母音、輔音字母的區分,用正則表達式取出羅馬字中的初聲、中聲、終聲部分n var x = initials.indexOf(matches[1]); // 在數組中查詢初聲的編號n var y = medials.indexOf(matches[2]); // 中聲編號n var z = finals.indexOf(matches[3]); // 終聲編號n s[i] = String.fromCharCode(0xAC00 + (x * 21 + y) * 28 + z);n // 按公式計算整字的 Unicode 編碼n }n return s.join();n}n
查詢初聲、中聲、終聲編號時使用了數組的 indexOf 方法,它需要遍曆數組,效率並不高。若要追求效率,可以使用哈希表。
程序執行效果如下:
我編寫的「漢字古今中外讀音查詢」安卓應用中,就有實現上述功能的代碼,見 Orthography.java 中的 Korean 類(目前在第 581 行)。
2.2 將韓語動詞或形容詞變為「思密達」形
在此,我們想編寫一個函數,輸入一個動詞或形容詞的原型,輸出它的「思密達」形。例如,輸入「??」,輸出「???」;輸入「???」,輸出「?????」。
先複習一下「思密達」形的構成規則:
- 首先去掉詞尾的 -?,得到詞幹;
- 如果詞幹以母音結尾,那麼加 -???(? 會與詞幹的最後一個字合成一個字);
- 如果詞幹以輔音結尾,那麼加 -???;
- 但是,如果詞幹以輔音 ? 結尾,則要去掉 ?,再加 -???(相當於 ? 代替了 ?)。
為了知道應該應用哪條規則,需要用程序獲取詞幹最後一個字的終聲。
變「思密達」形的函數如下:
function seubnida(s) {n var L = s.length; // 單詞總長度n var u = s.charCodeAt(L - 2); // 詞幹最後一個字的 Unicode 編碼n var z = (u - 0xAC00) % 28; // 詞幹最後一個字的終聲編號n if (z == 0 || z == 8) { // 如果沒有終聲(編號為 0)或終聲是?(編號為 8)n return s.substring(0, L - 2) + String.fromCharCode(u - z + 17) + ??;n // 取單詞的前 L-2 個字 將詞幹末字的終聲改成?(編號為 17) 再加??n }n else { // 如果終聲是其它輔音n return s.substring(0, L - 1) + ???;n // 取詞幹 再加???n }n}n
程序執行效果如下:
推薦閱讀:
※為什麼《訓民正音》將?定位為次清?
※用諺文給普通話注音有沒有可行性?
※為何韓國去漢字較之日本徹底?
※如何寫出漂亮的韓文?
※用諺文給滿語注音有沒有可行性?
TAG:韩语 | 谚文 | Unicode统一码 |