Поиск по сайту:

Используйте 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 используется чаще всего.