Используйте 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
И да, это все правильные слова. Например, «ахед» — это восклицание «аа» в прошедшем времени, как при расслаблении. А «аалии» — густой тропический кустарник.
Теперь мне просто нужно написать скрипт 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
выполнялся 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 используется чаще всего.