我最近开始编写一个游戏,你在其中使用字母方块构建单词。 为了创建这个游戏,我需要知道英语中常用词汇的字母频率,这样我才能提供一套有用的字母方块。 字母频率在很多地方都有讨论,包括维基百科,但我希望自己计算字母频率。
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 开始,如果未找到则返回零。 如果我有一个 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 打印。 如果另一个字母出现的频率是最低计数的两倍,则该字母的相对频率将为 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 实例,将打印字母 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 '^[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 是最常用的。
评论已关闭。