讓程序自動編寫程序

最近利用 gcin 的倉頡對照表寫了一個小程序。( 簡單來說就是一個 CLI 的倉頡轉換器 )
為什麼要做這個東西呢?當然這是為了可以在公司寫 blog 而不被人發現啊!想想如果明目張膽地於眾目睽睽之下打那麼多中文字,這很快就會被發現吧!


嘛,愉懶的事情先到這裏,今天想討論一下能將對照表編譯為源碼的技術。

對照表轉換的我在制作 wenku8 app 的時候,其中一環需為港台網民制作 簡=>繁 轉換1

由於簡繁不能直接對照2,於是我找到了新同文堂對照表,然後花了整整兩天時間將編譯方法苦了出來。

記得當時是基於效能問題得利用 C ++ 編寫這個程序,因為每篇文章可能有幾萬字,而每個中文字則有 4~6 個字節,轉換列表又有一萬多行,再加上轉換量,用 replace 實在不怎麼說得通。

於是我就寫起來了,在網上問到了「最快3」的轉換方法4後,剛剛開始寫的時候是這麼做的:
if( strcmp(str, "0海里") == 0 )
{
	out = "0海里";
	step_size = 8;
}
else if ( strcmp(str, "0天后") == 0 )
{
	out = "0天後";
	step_size = 8;
}
...
這個方法要用源碼編譯出來不難,可借這方法是蠢過頭了。於計算時間上來說是完全不科學的做法,如果每個字都是在最後一個判定取得的話,速度會慢得要死。

於是我想了個巢狀判定 ( nested if ) 的方法:
if( str[start + 0] == 239 && str[start + 1] == 188 )
{
	if( str[start + 2] == 144 )
	{
		if( str[start + 3] == 232 && str[start + 4] == 163 && str[start + 5] == 161 )
		{
			out = "0里";
			step_size = 5;
		}
		else if ( str[start + 3] == 230 && str[start + 4] == 181 && str[start + 5] == 183 )
		{
			if( str[start + 6] == 232 && str[start + 7] == 163 && str[start + 8] == 161 )
			{
				out = "0海里";
				step_size = 8;
			}
			if( step_size == 0 )
			{
				out = "0海";
				step_size = 5;
			}
		}
		else if ( str[start + 3] == 228 && str[start + 4] == 189 && str[start + 5] == 153 )
		{
			out = "0餘";
			step_size = 5;
		}
	}
...

這個方法可以稱為最少判定量了,每個判定都不會在下一個判定中重複5

但惡夢就在這裏開始了—我要怎麼讓程序知道什麼時候要包在裏面,什麼時候要 return 呢?

當腦袋要快要超載的時候,我將想法畫了出來:
左邊的I字相對於右邊的數組 ( 吻合顏色 ) ,由左至右順序處理。
表示式為: -> RGMMMO GMM RGMMM G ->
R: 紅 ( red )
G: 綠 ( green )
M: 洋紅 ( magenta )
O: 橙 ( orange )

在說明之前,先得了解中文字是屬於多元 ( multi-byte ) 字碼,畢竟要將一萬多個中文字以兩個 F6來表示怎麼想都不太可能。

而上面的數字就代表每個 byte 的值。在 UTF-8 上多數中文字大概位於三個字元的範圍7

由於每個 F 只會判定一次,因此要將不同的 byte 的值分組:
// 以下三個字組有相同的F值(239),因此239為一個判定。
// 下面在細字子字組,如此類推。
// 239 240 221
// 239 241
// 239 250

if( 239 )
{
  if ( 240 && 221 )
  {
     // output
  }
  else if( 241 )
  {
    ...
  }
  else if( 250 )
  .
  .
  .
}
...

分組亦有其巧妙之處,詞組表中有很多這樣的情況:
不干, 不干
不干不淨, 不乾不淨
不干他, 不干他
不干休, 不干休
不干你, 不干你
其中「不干」下面會有接續8,因此源碼得編成這樣:
if( 不干 )
{
   if ( 不淨 )
   {
      // 不乾不淨
      step_size = 8;
   }
   else if ( 他 )
   {
      // 不干他
      step_size = 6;
   }
   ...

   // 這裏判定如沒有接續
   if ( step_size = 0 )
   {
     // 不干
   }
   
}

因此分組的排列算法必須準確!

概念想好了,接下來是實作,沒想到這居然是最難的地方啊:

初期源碼 ( php ),( 後期版本已改為 Class ( ByteMap ),將往後發佈 ):

寫好之後做了幾次測試,效果拔群。

可惜文庫因為安全性方面的考量而打消了這個方法,這樣就過了半年……

就在我整理這些「雜物」時找到了這個程序,亦正在煩惱著沒什麼時間寫 blog ,該如何在公司寫而不讓人發現的方法時。突然靈機一觸「倉頡碼也是對照表吧!」,便動手將它寫成用途更廣範的 Class ( ByteMap ) 。花了兩天時間給做出來了!
  1. 其實最後文庫方拾棄了這個辦法,最後改用php自帶的strtr來做轉換了(這個方法在php 5.5之後的確比較快。於是這兩天苦出來的東西卻變成無用功了)。
  2. 簡體可以說是繁體的「有損壓縮」,繁體的「發」、「髮」相等於簡體的「发」。單一個「发」並不能只轉換成「發」或者「髮」。就像mp3一樣,某些資息被刪掉後無法回頭了。
  3. 其實還有另一種方法, PHP 的 strtr 是以存取記憶體住址作轉換的。
  4. 其實就是最蠢的方法:每個字詞都以IF作判段輸出,在編譯為機器碼時編譯器就會懂得如何「最佳化」。
  5. 像詞組「發光」、「發熱」,之前的方法會在「發」這個字重被做判段,效能上不可取。
  6. 就是指 0xFF ( 1 byte )
  7. 新同文堂的列表是有分「字列表」跟「詞組列表」的,因此 byte 的長度可多於三個 byte 。
  8. 我把這個叫 做 whole-byte sequence
Tag(s): c++ modeling php
Profile picture
斟酌 鵬兄
Thu Feb 06 2014 16:14:02 GMT+0000 (Coordinated Universal Time)
Last modified: Fri Mar 11 2016 14:32:53 GMT+0000 (Coordinated Universal Time)
Comments
No comments here.
Do you even comment?
website: 
Not a valid website
Invalid email format
Please enter your email
*Name: 
Please enter a name
Submit
抱歉,Google Recaptcha 服務被牆掉了,所以不能回覆了