使用 awk 統(tǒng)計字母頻率
近一段時間,我開始編寫一個小游戲,在這個小游戲里,玩家使用一個個字母塊來組成單詞。編寫這個游戲之前,我需要先知道常見英文單詞中每個字母的使用頻率,這樣一來,我就可以找到一組更有用的字母塊。字母頻次統(tǒng)計在很多地方都有相關(guān)討論,包括在 維基百科 上,但我還是想要自己來實現(xiàn)。
Linux 系統(tǒng)在 ??/usr/share/dict/words?
? 文件中提供了一個單詞列表,所以我已經(jīng)有了一個現(xiàn)成的單詞列表。然而,盡管這個 ??words?
? 文件包含了很多我想要的單詞,卻也包含了一些我不想要的。我想要的單詞首先不能是復(fù)合詞(即不包含連接符和空格的單詞),也不能是專有名詞(即不包含大寫字母單詞)。為了得到這個結(jié)果,我可以運行 ??grep?
? 命令來取出只由小寫字母組成的行:
$ grep '^[a-z]*$' /usr/share/dict/words
這個正則表達(dá)式的作用是讓 ??grep?
? 去匹配僅包含小寫字母的行。表達(dá)式中的字符 ??^?
? 和 ??$?
? 分別代表了這一行的開始和結(jié)束。??[a-z]?
? 分組僅匹配從 “a” 到 “z” 的小寫字母。
下面是一個輸出示例:
$ grep '^[a-z]*$' /usr/share/dict/words | head
a
aa
aaa
aah
aahed
aahing
aahs
aal
aalii
aaliis
沒錯,這些都是合法的單詞。比如,“aahed” 是 “aah” 的過去式,表示在放松時的感嘆,而 “aalii” 是一種濃密的熱帶灌木。
現(xiàn)在我只需要編寫一個 ??gawk?
? 腳本來統(tǒng)計出單詞中各個字母出現(xiàn)的次數(shù),然后打印出每個字母的相對頻率。
字母計數(shù)
一種使用 ??gawk?
? 來統(tǒng)計字母個數(shù)的方式是,遍歷每行輸入中的每一個字符,然后對 “a” 到 “z” 之間的每個字母進行計數(shù)。??substr?
? 函數(shù)會返回一個給定長度的子串,它可以只包含一個字符,也可以是更長的字符串。比如,下面的示例代碼能夠取到輸入中的每一個字符 ??c?
?:
{
len = length($0); for (i = 1; i <= len; i++) {
c = substr($0, i, 1);
}
}
如果使用一個全局字符串變量 ??LETTERS?
? 來存儲字母表,我就可以借助 ??index?
? 函數(shù)來找到某個字符在字母表中的位置。我將擴展 ??gawk?
? 代碼示例,讓它在輸入數(shù)據(jù)中只取范圍在 “a” 到 “z” 的字母:
BEGIN { LETTERS = "abcdefghijklmnopqrstuvwxyz" }
{
len = length($0); for (i = 1; i <= len; i++) {
c = substr($0, i, 1);
ltr = index(LETTERS, c);
}
}
需要注意的是,??index?
? 函數(shù)將返回字母在 ??LETTERS?
? 字符串中首次出現(xiàn)的位置,第一個位置返回 1,如果沒有找到則返回 0。如果我有一個大小為 26 的數(shù)組,我就可以利用這個數(shù)組來統(tǒng)計每個字母出現(xiàn)的次數(shù)。我將在下面的示例代碼中添加這個功能,每當(dāng)一個字母出現(xiàn)在輸入中,我就讓它對應(yīng)的數(shù)組元素值增加 1(使用 ??++?
?):
BEGIN { LETTERS = "abcdefghijklmnopqrstuvwxyz" }
{
len = length($0); for (i = 1; i <= len; i++) {
c = substr($0, i, 1);
ltr = index(LETTERS, c);
if (ltr > 0) {
++count[ltr];
}
}
}
打印相對頻率
當(dāng) ??gawk?
? 腳本統(tǒng)計完所有的字母后,我希望它能輸出每個字母的頻率。畢竟,我對輸入中各個字母的個數(shù)沒有興趣,我更關(guān)心它們的 相對頻率。
我將先統(tǒng)計字母 “a” 的個數(shù),然后把它和剩余 “b” 到 “z” 字母的個數(shù)比較:
END {
min = count[1]; for (ltr = 2; ltr <= 26; ltr++) {
if (count[ltr] < min) {
min = count[ltr];
}
}
}
在循環(huán)的最后,變量 ??min?
? 會等于最少的出現(xiàn)次數(shù),我可以把它為基準(zhǔn),為字母的個數(shù)設(shè)定一個參照值,然后計算打印出每個字母的相對頻率。比如,如果出現(xiàn)次數(shù)最少的字母是 “q”,那么 ??min?
? 就會等于 “q” 的出現(xiàn)次數(shù)。
接下來,我會遍歷每個字母,打印出它和它的相對頻率。我通過把每個字母的個數(shù)都除以 ??min?
? 的方式來計算出它的相對頻率,這意味著出現(xiàn)次數(shù)最少的字母的相對頻率是 1。如果另一個字母出現(xiàn)的次數(shù)恰好是最少次數(shù)的兩倍,那么這個字母的相對頻率就是 2。我只關(guān)心整數(shù),所以 2.1 和 2.9 對我來說是一樣的(都是 2)。
END {
min = count[1]; for (ltr = 2; ltr <= 26; ltr++) {
if (count[ltr] < min) {
min = count[ltr];
}
}
for (ltr = 1; ltr <= 26; ltr++) {
print substr(LETTERS, ltr, 1), int(count[ltr] / min);
}
}
最后的完整程序
現(xiàn)在,我已經(jīng)有了一個能夠統(tǒng)計輸入中各個字母的相對頻率的 ??gawk?
? 腳本:
# 只統(tǒng)計 a-z 的字符,忽略 A-Z 和其他的字符
BEGIN { LETTERS = "abcdefghijklmnopqrstuvwxyz" }
{
len = length($0); for (i = 1; i <= len; i++) {
c = substr($0, i, 1);
ltr = index(LETTERS, c);
if (ltr < 0) {
++count[ltr];
}
}
}
# 打印每個字符的相對頻率
END {
min = count[1]; for (ltr = 2; ltr <= 26; ltr++) {
if (count[ltr] < min) {
min = count[ltr];
}
}
for (ltr = 1; ltr <= 26; ltr++) {
print substr(LETTERS, ltr, 1), int(count[ltr] / min);
}
}
我將把這段程序保存到名為 ??letter-freq.awk?
? 的文件中,這樣一來,我就可以在命令行中更方便地使用它。
如果你愿意的話,你也可以使用 ??chmod +x?
? 命令把這個文件設(shè)為可獨立執(zhí)行。第一行中的 ??#!/usr/bin/gawk -f?
? 表示 Linux 會使用 ??/usr/bin/gawk?
? 把這個文件當(dāng)作一個腳本來運行。由于 ??gawk?
? 命令行使用 ??-f?
? 來指定它要運行的腳本文件名,你需要在末尾加上 ??-f?
?。如此一來,當(dāng)你在 shell 中執(zhí)行 ??letter-freq.awk?
?,它會被解釋為 ??/usr/bin/gawk -f letter-freq.awk?
?。
接下來我將用幾個簡單的輸入來測試這個腳本。比如,如果我給我的 ??gawk?
? 腳本輸入整個字母表,每個字母的相對頻率都應(yīng)該是 1:
$ echo abcdefghijklmnopqrstuvwxyz | gawk -f letter-freq.awk
a 1
b 1
c 1
d 1
e 1
f 1
g 1
h 1
i 1
j 1
k 1
l 1
m 1
n 1
o 1
p 1
q 1
r 1
s 1
t 1
u 1
v 1
w 1
x 1
y 1
z 1
還是使用上述例子,只不過這次我在輸入中添加了一個字母 “e”,此時的輸出結(jié)果中,“e” 的相對頻率會是 2,而其他字母的相對頻率仍然會是 1:
$ echo abcdeefghijklmnopqrstuvwxyz | gawk -f letter-freq.awk
a 1
b 1
c 1
d 1
e 2
f 1
g 1
h 1
i 1
j 1
k 1
l 1
m 1
n 1
o 1
p 1
q 1
r 1
s 1
t 1
u 1
v 1
w 1
x 1
y 1
z 1
現(xiàn)在我可以跨出最大的一步了!我將使用 ??grep?
? 命令和 ??/usr/share/dict/words?
? 文件,統(tǒng)計所有僅由小寫字母組成的單詞中,各個字母的相對使用頻率:
$ grep '^[a-z]*$' /usr/share/dict/words | gawk -f letter-freq.awk
a 53
b 12
c 28
d 21
e 72
f 7
g 15
h 17
i 58
j 1
k 5
l 36
m 19
n 47
o 47
p 21
q 1
r 46
s 48
t 44
u 25
v 6
w 4
x 1
y 13
z 2
在 ??/usr/share/dict/words?
? 文件的所有小寫單詞中,字母 “j”、“q” 和 “x” 出現(xiàn)的相對頻率最低,字母 “z” 也使用得很少。不出意料,字母 “e” 是使用頻率最高的。