2013年9月3日 星期二

Vim 字數計算

投稿要控制字數,判斷自己工作量也得靠字數,字數計算相當重要。那麼,Vim 要如何計算中文字數呢?

雖然網上能搜到不少作法,但這大把作法,在計算中英混雜的文章字數時,計算結果總是差得叫人想哭……

因為好像沒有笨蛋願意費工夫研究這個,那就由我來跳坑啦。

撲通![1]



程式碼


請將以下內容貼入您的 vimrc 中……

"=====================================================
"字數計算 Civa 版:計算 CJKV 文字 + CJKV 標點 + 英文字 字數
"   作者:Civa Lin 林雪凡 - Version: 1.0
"
"   目標是和 word 類軟體算得一樣準!歡迎提交 patch!
"
"   CJKV 文字與標點,每個字符單獨算一個字。其他字符則每個 word 算一個字。
"   以下內容沒有計算在內:
"       - 英文半形標點不算在字數之內。
"       - 空白不算字數,注意全形空白「 」\u3000 也不算。
"       - 少數特殊標點符號:如「…」\u2026 與「—」\u2014 等極少數符號,
"         因為和英文標點放在同區中,故也未算在中文標點之內,有需要可自行添加。
"       - 「中日韓統一表意文字擴充區」中的罕用字沒有算在內,因為搜尋字串會
"         太長 Vim 不執行 (至少 7.3 是如此)……囧rz
"
"   參考資料見:
"       - 基本說明:  http://chukaml.tripod.com/linguistics/han/index.html
"       - 碼表:      http://chukaml.tripod.com/linguistics/unicode/index.html
"       - 字符說明:
"         http://chukaml.tripod.com/linguistics/unicode/codeChart/index.html
"
"   其他銘謝:
"       - 早期框架來源:  http://kenshin54.iteye.com/blog/876203
function! CalWordCount()
    "以下模式中各段落……
    "\<\w*\> 為英文字
    let l:cmd = []
    "頭部
    let l:cmd = add(l:cmd, '%s/')
    let l:cmd = add(l:cmd, '\(')
    "單純字元比對
    let l:cmd = add(l:cmd, '[')
    "3000 ~ 303f = 「中日韓符號和標點」
    let l:cmd = add(l:cmd, '\u3001-\u303f') "\u3000為全形空白
    "3040 ~ 309f = 「日文假名」
    let l:cmd = add(l:cmd, '\u3040-\u309f') "平假名
    let l:cmd = add(l:cmd, '\u30a0-\u30ff') "片假名
    "3100 ~ 312f = 「注音符號」,31A0 ~ 31BF = 「注音符號擴充」
    let l:cmd = add(l:cmd, '\u3100-\u312f')
    let l:cmd = add(l:cmd, '\u31a0-\u31bf')
    "4e00 ~ 9fff = 「中日韓統一表意文字」
    let l:cmd = add(l:cmd, '\u4e00-\u4eff\u4f00-\u4fff\u5000-\u50ff\u5100-\u51ff\u5200-\u52ff\u5300-\u53ff\u5400-\u54ff\u5500-\u55ff\u5600-\u56ff\u5700-\u57ff\u5800-\u58ff\u5900-\u59ff\u5a00-\u5aff\u5b00-\u5bff\u5c00-\u5cff\u5d00-\u5dff\u5e00-\u5eff\u5f00-\u5fff\u6000-\u60ff\u6100-\u61ff\u6200-\u62ff\u6300-\u63ff\u6400-\u64ff\u6500-\u65ff\u6600-\u66ff\u6700-\u67ff\u6800-\u68ff\u6900-\u69ff\u6a00-\u6aff\u6b00-\u6bff\u6c00-\u6cff\u6d00-\u6dff\u6e00-\u6eff\u6f00-\u6fff\u7000-\u70ff\u7100-\u71ff\u7200-\u72ff\u7300-\u73ff\u7400-\u74ff\u7500-\u75ff\u7600-\u76ff\u7700-\u77ff\u7800-\u78ff\u7900-\u79ff\u7a00-\u7aff\u7b00-\u7bff\u7c00-\u7cff\u7d00-\u7dff\u7e00-\u7eff\u7f00-\u7fff\u8000-\u80ff\u8100-\u81ff\u8200-\u82ff\u8300-\u83ff\u8400-\u84ff\u8500-\u85ff\u8600-\u86ff\u8700-\u87ff\u8800-\u88ff\u8900-\u89ff\u8a00-\u8aff\u8b00-\u8bff\u8c00-\u8cff\u8d00-\u8dff\u8e00-\u8eff\u8f00-\u8fff\u9000-\u90ff\u9100-\u91ff\u9200-\u92ff\u9300-\u93ff\u9400-\u94ff\u9500-\u95ff\u9600-\u96ff\u9700-\u97ff\u9800-\u98ff\u9900-\u99ff\u9a00-\u9aff\u9b00-\u9bff\u9c00-\u9cff\u9d00-\u9dff\u9e00-\u9eff\u9f00-\u9fff')
    "fe10 ~ fe1f = 「豎式標點」
    let l:cmd = add(l:cmd, '\ufe10-\ufe1f')
    "FF00 ~ FFEF = 「全形與半形字符」
    let l:cmd = add(l:cmd, '\uff00-\uffef')
    let l:cmd = add(l:cmd, ']')
    "單純字元比對結束
    " 英文字 (word) 匹配
    let l:cmd = add(l:cmd, '\|')
    let l:cmd = add(l:cmd, '\<\w*\>')
    "尾部
    let l:cmd = add(l:cmd, '\)')
    let l:cmd = add(l:cmd, '/&/gn')

    exe join(l:cmd, '')
endfunction

"命令模式下 c 調用,因為速度很慢,要選個不容易誤觸的
map c :call CalWordCount()

"命令模式下 <leader>c 調用,因為速度很慢,要選個不容易誤觸的
map <leader>c :call CalWordCount()<cr>
然後挑篇文章,在 Normal Mode 中按 <leader>c 就能爽快計算字數了。

內容很長……沒錯,不過我是故意用 add() 分行才寫這麼長的,這樣才好維護抓錯啊。不要吐嘈這個啦。另外像是 4e00 ~ 9fff 分成那麼多行,則是因為 Vim 7.3 似乎只支援 8 bit 的字元範圍設定,所以才被迫拆成那種難看樣……[2]

在 Vim 7.3 中,這個計算速度很慢,平均每千中文字要花 1 秒,而一萬字就要差不多十秒……這我沒啥好辦法,還請等它一下。幸好 Vim 7.4 更新了 regex 引擎,速度就變得爆快,萬字文章也能瞬秒,還沒升級的請儘快升級去,絕不吃虧的啦。

精度實驗


測試案例援用我寫的這篇文章:《 Ren'Py 心得提示第六回:Displayable 與動畫轉置語言 》……將其轉貼到 txt 檔案中,然後對這個 txt 計算字數。選這篇是因為它夠長,而且也混雜了一堆中英文與程式碼,比較複雜的緣故。

字數測試結果

字數測試結果

以 MS Word 為標準,誤差是千分之五……嗯,馬馬虎虎啦;不過居然和 Apache OpenOffice 一字不差倒是奇事一樁。是用了相同的算法嗎……

無論如何,還請將就拿去用吧!也歡迎提交 patch 給我!



[1] …… 快救我啊!要溺死啦咕嚕咕嚕……
[2] 但此一特徵似乎在 Vim 7.4 中改變了。7.4 中可以直接寫成 \u4000-\u9fff 這樣,真是媽祖保佑天降福音。也許咱從下一版開始就會這樣寫。

沒有留言:

張貼留言

☆每日吐嘈,有益身心☆
…不過還是請手下留情別太狠啊。