Vim 字數計算

從事文字相關工作,字數計算相當重要。那麼,Vim 要如何計算中文字數呢?

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

因為好像沒有哪個傢伙願意費工夫研究這個,那就由我先跳坑啦。

撲通!

程式碼

請將以下內容貼入您的 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
"命令模式下 <leader>c 調用,因為速度很慢,要選個不容易誤觸的
map <leader>c :call CalWordCount()

然後挑篇文章,在 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 給我!


  • [2] 但此一特徵似乎在 Vim 7.4 中改變了。7.4 中可以直接寫成 \u4000-\u9fff 這樣,真是媽祖保佑。也許咱從下一版開始就會這樣寫。