最近讀的 Udacity CS101 ,程度不太難,可以有時間兼顧另一個 course 。我的時間有限,於是只選了最有興趣的 NLP 來讀
入讀後才發現自己非常膚淺,人家說我無料,似乎是真的。我的電腦技能連人家頂級學府的 undergrad 也不如。
例如天天用 Linux ,但是 Unix command line 的技巧很弱。這代表甚麼?就是天天只是在用 GUI ,或者是天天渾渾噩噩虛渡光陰。
今天想寫下最近學會用的幾個 utilities : tr, sort, uniq 等等。寫 Unix 是無可能寫得完的。
例如有如此的 text file (檔名是 pg1342.txt,是 Jane Austen 的 PRIDE AND PREJUDICE 的電子書),怎樣分析文字的 distribution 。
Unix 的 wc 指令可計算出行數、字數等等資料。如

chainsaw@revo2:~/Dropbox$ wc pg1342.txt
13426 124588 717571 pg1342.txt

回報的三個數字分別是行數、字數和 byte 數。可加入如 -w 去只列出字數。
如果想知道 Pride and Predudice 最常見的字,就要花不少功夫。包括以下工序:

  1. Tokenize
  2. Sort
  3. Remove duplicates

Tokenize 主要是用 tr 這個東西達成。 tr 是 transliterate 之意。先做幾個實驗。

chainsaw@revo2:~/Dropbox$ echo 'abc de' | tr 'a' 'b'
bbc de

先用 echo 製一個 text stream 'abc de'。再 pipe 到 tr 把 'a' 轉成 'b' ,故此結果是 'bbc de' 。 結果 tr 如預期把 a 轉成 b 。另一個實驗如此:

chainsaw@revo2:~/Dropbox$ echo 'abc de' | tr -c 'a' 'b'
abbbbbbb

加入 -c (C for Complement, = invert),卻是將 'a' 以外的任何東西都變成 'b' 。
將 echo 嘔出的 stream 改少少, tr 的 parameter 也改少少。

chainsaw@revo2:~/Dropbox$ echo 'ab,c de.1212' | tr -c 'a-z' '\n'
ab
c

de

如此指令是將 a-z 以外的所有東西(如空格逗點句號)變成 newline (\n) 。newline 太多,可加入 -s 將重覆的 newline 踢走。

chainsaw@revo2:~/Dropbox$ echo 'ab,c de.1212' | tr -cs 'a-z' '\n'
ab
c
de

現在可以 tokenize Pride and Predudice 。

chainsaw@revo2:~/Dropbox$ tr -cs 'a-zA-Z' '\n' < pg1342.txt | more

用箭咀把 tr 的輸入由 default 的 STDIN (如 Keyboard 輸入) 變成 txt file 。再將結果 pipe 到 more 。由結果所見,文章變成了一個個字。 more 可按 q 跳出。
之後將 tr 的結果 pip 到 sort

chainsaw@revo2:~/Dropbox$ tr -cs 'a-zA-Z' '\n' < pg1342.txt | sort | more

sort 只是將每一個字排序。 default 是根據文字字首排。
sort 完之後可以用 uniq -c 數數目。

chainsaw@revo2:~/Dropbox$ tr -cs 'a-zA-Z' '\n' < pg1342.txt | sort | uniq -c | more

從結果的頭三行可見到問題所在。就是 A 和 a 分成兩個不同的字。大細階如 About 和 about 也當作不同的字。這個問題可以用 tr 將大階的 A-Z 變成細階 a-z 。才再將 text stream 輸入到另一次 tr ,進行 tokenize 。

chainsaw@revo2:~/Dropbox$ tr 'A-Z' 'a-z' < pg1342.txt | tr -cs 'a-zA-Z' '\n' | sort | uniq -c | more

從結果可見大細階的問題解決了。但另有一些問題,這個問題日後才解答。 ((此問題是 abominable 和 abominably 分成兩個字了。有些情況會將此兩字當成同一個字,因為他們的字根一樣。這就要先將文字進行 stemming 才再輸入到 tr 。))
uniq -c 的 output 第一個 field 是字的出現次數。可以再用 sort 把字的出現次數排序。

chainsaw@revo2:~/Dropbox$ tr 'A-Z' 'a-z' < pg1342.txt | tr -cs 'a-zA-Z' '\n' | sort | uniq -c | sort -nr | more

從數據可見, the 出現了 4507 次排榜首。最高出現次數的英文名是 elizabeth ,有 635 次。 darcy 只出現了 418 次。
又例如 bennet 家族有 elizabeth, jane, mary, catherine 和 lydia 。可用 grep 把這些名抽出來。

chainsaw@revo2:~/Dropbox$ tr 'A-Z' 'a-z' < pg1342.txt | tr -cs 'a-zA-Z' '\n' | sort | uniq -c | sort -nr | grep "elizabeth\|jane\|mary\|catherine\|lydia"
635 elizabeth
295 jane
171 lydia
126 catherine
39 mary

由此表可見 bennet 字族名稱的出現次數。 ((但這是不準確的,因為 elizabeth 在文中有時叫 lizzy 。 catherine 有時叫 kitty 。))