Linux中國

使用 awk 統計字母頻率

近一段時間,我開始編寫一個小遊戲,在這個小遊戲里,玩家使用一個個字母塊來組成單詞。編寫這個遊戲之前,我需要先知道常見英文單詞中每個字母的使用頻率,這樣一來,我就可以找到一組更有用的字母塊。字母頻次統計在很多地方都有相關討論,包括在 維基百科 上,但我還是想要自己來實現。

Linux 系統在 /usr/share/dict/words 文件中提供了一個單詞列表,所以我已經有了一個現成的單詞列表。然而,儘管這個 words 文件包含了很多我想要的單詞,卻也包含了一些我不想要的。我想要的單詞首先不能是複合詞(即不包含連接符和空格的單詞),也不能是專有名詞(即不包含大寫字母單詞)。為了得到這個結果,我可以運行 grep 命令來取出只由小寫字母組成的行:

$ grep  '^[a-z]*$' /usr/share/dict/words

這個正則表達式的作用是讓 grep 去匹配僅包含小寫字母的行。表達式中的字元 ^$ 分別代表了這一行的開始和結束。[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」 是一種濃密的熱帶灌木。

現在我只需要編寫一個 gawk 腳本來統計出單詞中各個字母出現的次數,然後列印出每個字母的相對頻率。

字母計數

一種使用 gawk 來統計字母個數的方式是,遍歷每行輸入中的每一個字元,然後對 「a」 到 「z」 之間的每個字母進行計數。substr 函數會返回一個給定長度的子串,它可以只包含一個字元,也可以是更長的字元串。比如,下面的示例代碼能夠取到輸入中的每一個字元 c

{
    len = length($0); for (i = 1; i <= len; i++) {
        c = substr($0, i, 1);
    }
}

如果使用一個全局字元串變數 LETTERS 來存儲字母表,我就可以藉助 index 函數來找到某個字元在字母表中的位置。我將擴展 gawk 代碼示例,讓它在輸入數據中只取範圍在 「a」 到 「z」 的字母:

BEGIN { LETTERS = "abcdefghijklmnopqrstuvwxyz" }

{
    len = length($0); for (i = 1; i <= len; i++) {
        c = substr($0, i, 1);
        ltr = index(LETTERS, c);
    }
}

需要注意的是,index 函數將返回字母在 LETTERS 字元串中首次出現的位置,第一個位置返回 1,如果沒有找到則返回 0。如果我有一個大小為 26 的數組,我就可以利用這個數組來統計每個字母出現的次數。我將在下面的示例代碼中添加這個功能,每當一個字母出現在輸入中,我就讓它對應的數組元素值增加 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];
        }
    }
}

列印相對頻率

gawk 腳本統計完所有的字母后,我希望它能輸出每個字母的頻率。畢竟,我對輸入中各個字母的個數沒有興趣,我更關心它們的 相對頻率

我將先統計字母 「a」 的個數,然後把它和剩餘 「b」 到 「z」 字母的個數比較:

END {
    min = count[1]; for (ltr = 2; ltr <= 26; ltr++) {
        if (count[ltr] < min) {
            min = count[ltr];
        }
    }
}

在循環的最後,變數 min 會等於最少的出現次數,我可以把它為基準,為字母的個數設定一個參照值,然後計算列印出每個字母的相對頻率。比如,如果出現次數最少的字母是 「q」,那麼 min 就會等於 「q」 的出現次數。

接下來,我會遍歷每個字母,列印出它和它的相對頻率。我通過把每個字母的個數都除以 min 的方式來計算出它的相對頻率,這意味著出現次數最少的字母的相對頻率是 1。如果另一個字母出現的次數恰好是最少次數的兩倍,那麼這個字母的相對頻率就是 2。我只關心整數,所以 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);
    }
}

最後的完整程序

現在,我已經有了一個能夠統計輸入中各個字母的相對頻率的 gawk 腳本:

#!/usr/bin/gawk -f

# 只統計 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 命令把這個文件設為可獨立執行。第一行中的 #!/usr/bin/gawk -f 表示 Linux 會使用 /usr/bin/gawk 把這個文件當作一個腳本來運行。由於 gawk 命令行使用 -f 來指定它要運行的腳本文件名,你需要在末尾加上 -f。如此一來,當你在 shell 中執行 letter-freq.awk,它會被解釋為 /usr/bin/gawk -f letter-freq.awk

接下來我將用幾個簡單的輸入來測試這個腳本。比如,如果我給我的 gawk 腳本輸入整個字母表,每個字母的相對頻率都應該是 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」,此時的輸出結果中,「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

現在我可以跨出最大的一步了!我將使用 grep 命令和 /usr/share/dict/words 文件,統計所有僅由小寫字母組成的單詞中,各個字母的相對使用頻率:

$ grep  &apos;^[a-z]*$&apos; /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」 出現的相對頻率最低,字母 「z」 也使用得很少。不出意料,字母 「e」 是使用頻率最高的。

via: https://opensource.com/article/21/4/gawk-letter-game

作者:Jim Hall 選題:lujun9972 譯者:lkxed 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國