Jigsaw

Jigsaw

Learn everything, do nothing.

如何製作一款字形輸入法

聲明:本文不是軟體開發教程,並不會教您開發「某狗輸入法」「某度輸入法」之類的輸入法軟體。本文不涉及程式碼,僅起思路上的拋磚引玉之用,歡迎各位輸入法先達指教。

如果您從未接觸過字形輸入法,可先閱讀北鸮的這兩篇文章:嘗試了七種形碼輸入法後,我想聊聊在 2022 年用五筆這件事為了打字更爽,我學了一個追求極致性能的小眾輸入法。本文前言也作了簡短介紹,仍有不懂可留言,我會再補充。

關於本人:聊天打字用星空鍵道,書面打字用三徐(自用徐碼改版),打單速度 60 字每分鐘。之前淺學過小鶴音形倉頡五代虎碼,因需求不同而放棄。

前言#

漢字輸入法的定義#

其實很簡單:輸入編碼,輸出漢字集合的函數就是漢字輸入法。

假設 $Z$ 為 $n$ 個漢字的集合,$C$ 是 $l$ 個 $Z$ 子集的集合,$c_i$ 是 $C$ 的第 $i$ 個元素,$M$ 為 $l$ 個編碼的集合,$f: M \rightarrow C$ 即輸入法。

如今簡化字使用區大部分人用的都是漢語拼音輸入法 —— 輸入漢語拼音,輸出漢字;少部分人使用五筆字型輸入法 —— 輸入由 A 到 Y 組成、長度小於四的編碼,輸出漢字。

在傳統漢字使用區,還有倉頡輸入法 —— 輸入由字母組成、長度小於五的編碼,輸出漢字;注音輸入法 —— 輸入由鍵盤上絕大多數字符組成的編碼,輸出漢字。

實際上,使用 Unicode 編碼打字也能算是一種輸入法。

字形輸入法與字音輸入法#

上文提到的漢語拼音、注音屬於使用漢字讀音進行編碼的字音輸入法(以下簡稱音碼);五筆、倉頡屬於使用漢字字形進行編碼的字形輸入法(以下簡稱形碼);小鶴音形的編碼方式先用字音再用字形,所以屬於音形碼。

如今,雖然搭載大詞庫的音碼已經能滿足日常交流的打字需求,形碼因其重碼低且不依賴讀音的特點仍具使用價值,故愛好者還在不斷研發新的形碼。

字根與拆分#

字根#

字根是形碼拆字的基本形狀單位。其與我們小學時學的偏旁、部首等概念相似,但字根的範圍往往大於偏旁、部首。

比如 86 版五筆輸入法共有 234 個字根,其中「王」「土」「日」都是我們熟悉的偏旁,而「炙」的上半部分、「互」的中間部分就顯得有些陌生了。

86 版五筆字根圖

在一些形碼中,結構複雜的漢字也可能設為字根以減輕用戶拆字負擔,比如徐碼裡「爾」「鹵」「黽」都是字根。

拆字#

拆字即使用已設置的字根拆分漢字。這實際上也是我們小學就經歷過的內容 —— 在學習生字時,語文老師會教我們它是由以前學過的字組成的。

如何拆分結構簡單的字,大家應該沒什麼意見:「葉」自然是拆成「口」和「十」,「音」自然是拆成「立」和「日」。但如果字的結構稍微複雜一點就難辦了:「戊」是拆「戈丿」,還是拆「廠㇂丿丶」呢?這時候就要引入限制拆分方式的規則,防止一個漢字出現多種拆分。

還是拿 86 五筆舉例:五筆中按優先級有兼顧直觀符合筆順取大優先能連不交能散不連這麼幾條規則。

先從取大優先、符合筆順講起:

  • 「世」可拆「一凵𠃊」和「廿𠃊」,因為後者的第一個字根比前者的第一個字根大,故拆「廿𠃊」。
  • 「夷」能拆「一弓人」和「大弓」,但因為「大弓」拆法不合筆順且合筆順優於取大,故拆「一弓人」。

世夷

能散不連、能連不交就是字面上的意思,字根不相連的拆法優於字根相連的拆法,字根相連的拆法優於字根相交的拆法。有的輸入法還會引入更進一步的能交不断,指字根相連交的拆法優於同一筆畫斷開的拆法。這三個規則合稱散連交斷

兼顧直觀則比較難懂,它在五筆中已經成為一種萬能規則,即作者主動認定什麼拆法直觀。比如下圖的「或」「戊」兩字,前者能不合筆順拆「戈口一」,後者卻得合筆順拆「廠㇂丿丶」。而在 86 五筆之中,這樣的問題比比皆是:為什麼「里」拆「日土」不拆「甲二」?為什麼「匹」拆「匚兒」不拆「兀𠃊」?

或戊

這種模糊不清的規則是萬萬要不得的,如今建議使用更清晰有理的規則。比如「匹」的拆分規則就可以用引入結構完整(「囗日目勹冂匚コ凵」等存在全包圍和半包圍結構的字根不拆散)來解釋。另外,還可引入原形字根(如果一個字根的的豎筆在作偏旁時變作撇,或橫筆在作偏旁時變作提,視其為優先級更低的變形字根)規則,也可解釋一些拆分歧義。

不過拆分規則並不是越細越好,像張碼為了降重把規則搞得十分複雜,為難的也是用戶;倉頡的拆分方式更是碼圈獨樹一幟。輸入法作者應在保證拆分唯一性的前提下制定最易理解的規則,以減輕用戶的心智負擔。

倉頡拆分一觀倉頡拆分一觀,截自維基倉頡教科書

在拆完常用 3500 字(通用規範漢字表一級字)後,我們便得到了一張可用的拆分表。

增刪字根#

五筆的作者王永民先生曾說過:「一般來說,一個使用 26 鍵的拼形方案,以選取 150~250 個字根為宜。」現在大多數新輸入法的字根歸併後也屬於此範疇,符合統計學原理。

在四碼方案中,參與單字編碼的根數往往只有前三根和最末根,可以說中間這些字根實際上沒有用處。如果一個字根從來沒有參與過編碼,當然可以直接刪除此根;如果有多個字的前三末一字根都相同,如「贏嬴贏」,就要考慮增加字根以取到不同的根,將重碼扼殺在拆分這一步。

如果你想了解在拆分階段進行定量分析的更多理論依據,可參考藍落蕭的文章拆分表的定量評價


在這裡也幫藍落蕭打個廣告:如果你對製作形碼有興趣,看到此處仍不得要領,請考慮使用由藍落蕭主持開發的漢字自動拆分系統(請無視註冊功能,選擇示例即可進入主界面),全 UI 操作沒有門檻。

漢字自動拆分系統

編碼方式#

字根編碼#

對字根進行編碼,是對單字編碼的前提。字根編碼一般會有一定規律,比如五筆用首兩筆在 QWERTY 鍵盤上分區,鄭碼用首兩筆對應字母序,倉頡用「日月金木水火土」對應字母序,表形碼用字根的形狀對應字母,這些用字形特徵對字根進行規律編碼的方式稱為形托。正如有音碼形碼,字根編碼也有音托形托,小鶴音形就是採用音托。

也有輸入法為了更好的性能指標,會使用完全無規律(下稱亂序)的方式編碼字根,比如藍寶石輸入法。

字根的編碼方式多種多樣,以下直接列舉:

  • 單編碼,以五筆為代表,每個字根用一個字母表示,比如「十二」的編碼都是 F,「五一」的編碼都是 G。
  • 雙編碼,絕大多數字根兩個字母表示,分別稱大碼、小碼。
    • 小碼形托,以鄭碼為代表,小碼與漢字的字形有關,比如「耳」的小碼為 E,因為其結構中包含「十」。
    • 小碼音托,小碼與漢字的讀音有關,比如徐碼中「自」的小碼為 Z,因為其漢語拼音為 zi。採用音托可減輕記憶量,使雙編碼字根的記憶難度無限逼近於單編碼。
  • 三編碼以上…… 事實上想要字根編碼多長都行,但如果用戶連字根的編碼都記不住,更別提之後的單字編碼。

單字編碼#

首先自然要決定一個字有多少字根參與編碼,本文採用主流形碼的取法,即前面提到的前三末一。字根不足四個怎麼辦?就要用漢字的其他字形特徵進行補碼,在五筆中是字根筆畫或漢字結構,在鄭碼中是字根的小碼;當然也可以用漢字的讀音補碼,不過那樣就成形音碼了。

比如像「呗員」「吧邑」這種兩個字根一模一樣的字,我們就需要使用結構碼來區分,上下結構補 B,左右結構補 N。但結構碼的引入又給用戶造成新的記憶點,這時雙編字根就可以派上用場 —— 可以在使用頻率較小的字的編碼後補上首字根的小碼,這樣二者的編碼也能分離。

假設一個漢字可以被拆分為若干字根,每個字根都有一個編碼,那麼該編碼用大寫拉丁字母 ABCD...WXYZ 編號。特別地,Y 和 Z 用來強調倒數第二、第一根。表示字根筆畫,使用小寫希臘字母 αβ...ω。特別地,ω 用來強調倒數第一筆畫。Ω 表示字形結構編碼。

則五筆的編碼表式為:

  • 單根字
    • 代表字根 AAAA
    • 非代表字根 Aαβω
  • 多根字
    • 兩根字 AZΩ
    • 三根字 ABZΩ
    • 四根及以上字 ABCZ

如果你想了解更多輸入法的編碼方式,可參考朱宇浩的文章常見形碼輸入方案編碼規則

詞語編碼(可選)#

如果你不想只打單字,詞語編碼就必不可少。好在輸入法界目前有公認的詞語編碼方式,無需費任何腦筋思考新方式。就像字根組成單字一樣,單字組成詞語,所以詞語的取碼方式和單字類似:

每個字都有一個編碼,那麼該編碼用大寫拉丁字母 ABCD...WXYZ 編號。特別地,Y 和 Z 用來強調倒數第二、第一根。字的第二編碼用對應的小寫拉丁字母 abcd...wxyz 編號。

則形碼的詞語編碼表式為:

  • 兩字 AaBb
  • 三字詞 ABCc
  • 四字及以上詞 ABCZ

性能調優#

性能指標#

坊間還流傳著五筆作者王永民先生的另一句話:「一個高水平的 “形碼” 方案,必須同時具備以上相容、規律、協調這三個特性。」所謂相容,即重碼低;所謂規律,即易學;所謂協調,即手感好 —— 這三者不可兼得。很多時候,輸入法是放棄了一項優勢來換取另一項優勢,比如現在賽文流行的亂序輸入法(虎碼、藍寶石、逸碼)就是放棄了規律來提升其他兩者,而我使用的徐碼很大程度上放棄了協調以迫求大字集的相容。

  • 靜態重碼數:遍歷所有編碼,每次輸出的漢字集合大小為二的子集數總和,反映相容性。
  • 動態重碼率:輸出的漢字集合按字頻排序,移除第一個元素,剩下的元素字頻的總和,反映相容性。
  • 平均碼長:編碼長度乘以漢字的字頻的總和,注意非首選字的編碼長度加一,與動態重碼率正相關。每分鐘輸入字數 = 每分鐘擊鍵數 / 平均碼長。
  • 速度當量:從二百多萬個實驗數據中統計分析的連續鍵位舒適度,詳細請參考論文鍵位相關速度當量的研究,反映協調性。
  • 雙手互擊率:輸入所有左右手交替擊鍵的編碼,輸出字集內字頻的總和,反映協調性。

還記前言裡對輸入法的數學定義嗎?我們可以由此推出以上指標的數學定義。

假定 $p: Z \rightarrow [0.1]$ 為漢字在某文本狀態下的單字頻率的映射,用字頻對 $C$ 中的每一字集排序,使 $c_{ij}$ 是編碼為是編碼為 $m_i$ 的第 $j$ 個漢字,$i \in I$, $j \in J_i$, 且滿足 $a\geq b$ 時,$p (c_{ia})\geq p (c_{ib})$.

  • 靜態重碼數:$N_{s} = \mid {c_{ia}, c_{ib} \text { for all } a,b \in J_i \text { and } i \in I }.$
  • 動態重碼率:$N_{d} = \sum\limits_{i \in I, j \in J_i/{1}} p (c_{ij}).$

如果不懂得計算這些性能指標,也可用線上的虎碼測評網

虎碼測評網

出簡讓全#

五筆的單字編碼和詞語編碼的長度都大於三,這是因為其在設計編碼規則時就為簡碼留下空間。簡碼即更簡短的編碼 ——「的」字的五筆編碼為 rqyy,但實際只要打 r 再按下空格就能上屏「的」。像「的」這種只保留前一码的簡碼稱作一級簡碼(簡稱一簡),以此類推則有二簡、三簡。

簡碼是提高輸入效率最簡單的方法。常用前 26 字的頻率總和為 0.26,如果所有單字的編碼都是四碼,剛好出滿一簡即可減短 0.5 的平均碼長;常用前 27-702 字的頻率總和為 0.57,也就是說出滿一二簡可減短 1.09 的平均碼長。而我們知道打字速度 = 擊鍵次數 / 碼長,所以理想狀態下出滿一二簡,打字速度提升了三分之一。

而出簡的好處遠不止如此。與簡碼相對,單字原來的編碼稱作全碼。既然該全碼碼位上頻率最高的字已經出簡,字頻排第二的字自然可以頂上,這種做法稱作讓全,即讓出已有簡碼的字的全碼碼位。這樣一來便能消除原有的重碼,即使三簡沒有碼長上的收益,也能用來降低重碼。

好處這麼多,代價是什麼?簡碼實際上是一種無理碼,出得越多用戶的記憶負擔就越重。一旦記不住就要浪費時間看候選框,反而降低擊鍵數,得不償失。建議只出一簡,二簡交給用戶自己設置或只出簡不讓全,別設三簡。

全局優化#

全局優化一般使用模擬退火算法,程序請參考模擬退火算法原理及應用輸入法優化、字頻、詞頻統計算法源程序分享宇浩輸入法開發技術文檔,本文不再多說。

由於退火算法的原理就是撞概率,所以設置一些約束條件(如不能在同一鍵位的字根與必在同一鍵位的字根)可以有效減少無用字根排布,進而提升算法效率。在設置約束條件可以多採納前輩的智慧,舉個例子:在設計三徐之前我一直很好奇,為什麼規律雙編形碼總要在一個大碼下要設置兩個小碼固定不規律的主根?後來自己著手優化後才發現,如果不出兩個主根重碼必然提高。

尾聲#

在你的輸入法大成之時,別忘了導出碼表供愛好者們使用:大多數輸入平台支持的碼表格式為每行 字\tab編碼,也有少數輸入平台是倒過來。如果對自己的輸入法有自信,可到五筆吧發帖宣傳。輸入法離不開社區的支持,有了用戶便可以根據反饋調優,使輸入法更加完美。

當然,密碼學有句老話叫「不要自己設計加密算法」,我認為輸入法亦是如此。真要設計之前,不妨先找一下市面上有沒有適合自己需求的輸入法,用現成的總是比較簡單。

閒話:選擇輸入法的哲學#

目前 Unicode 漢字收錄約十萬,也就是說電子設備裝上字體後能顯示十萬漢字,預計今後還會不斷增多(CJK-J 區新收錄四千多字)。這些漢字中,並不是所有漢字的讀音都流傳到了現在;還有部分字雖有讀音,但不去查字典基本不知道怎麼讀 —— 要打出這些字,就必須使用形碼。綜上所述,大字集重要嗎?還是不重要。就算你的名字中有生僻字,加這個字到碼表上就行了,沒必要尋找能打全字集的輸入法。就我個人而言,拆大字集是作為漢字愛好者的樂趣,並不是什麼剛需。

打字速度重要嗎?當然重要,但要想想自己有沒有毅力練習。打字速度的木桶效應十分明顯,而輸入法性能其實是其中最長的一板。想要提高跟打速度,不管你用什麼輸入法,唯有長時間的練習才能做到。不要想着選了性能最好的輸入法就萬事大吉,速度高手不是因為選擇了更好的輸入法而成為高手,而是速度高手為了更進一步發明了更好的輸入法。如果你連非水文生稿單字百速都達不到,談何輸入法性能?

簡繁通打重要嗎?當然不重要,小眾需求中的小眾需求。主要還是 OpenCC 轉換有所不足,有些異體字沒必要轉換:比如「羣」,我一直認為左右結構的「群」在電子設備上顯示效果更好。

說到底形碼重要嗎?其實也不重要。雙拼學習成本低,降低全拼碼長卻是立杆見影。輸入法是為了輸入文字而存在,文字所承載的知識才是人類通天的階梯

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。