使用awk计算字母频率
编写一个 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);
}
}请注意,索引函数返回 LETTERS 字符串中第一次出现的字母,第一个字母从 1 开始,如果没有找到则返回零。如果我有一个 26 个元素长的数组,我可以使用该数组来计算每个字母的出现次数。我会将其添加到我的代码示例中,以增加(使用 ++)输入中出现的每个字母的计数:
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 脚本对所有字母进行计数后,我想打印它找到的每个字母的频率。我对输入中每个字母的总数不感兴趣,而是对每个字母的相对频率感兴趣。相对频率对计数进行缩放,以便将出现次数最少的字母(例如字母 q)设置为 1,而其他字母则相对于此。
我将从字母 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 打印。如果另一个字母出现的频率是最低计数的两倍,则letter 的相对频率为 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
# only count a-z, ignore A-Z and any other characters
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];
}
}
}
# print relative frequency of each letter
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 实例,将打印相对频率为 2 的字母 e,而所有其他字母则为 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 '^[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 出现的频率最低。字母 z 也很少见。毫不奇怪,字母 e 是最常用的。