Utilisez awk pour calculer la fréquence des lettres
Écrivez un script awk pour déterminer les lettres les plus (et les moins) courantes dans un ensemble de mots.
J'ai récemment commencé à écrire un jeu dans lequel vous construisez des mots à l'aide de tuiles de lettres. Pour créer le jeu, j'avais besoin de connaître la fréquence des lettres dans les mots ordinaires de la langue anglaise, afin de pouvoir présenter un ensemble utile de tuiles de lettres. La fréquence des lettres est discutée à divers endroits, y compris sur Wikipédia, mais je voulais calculer moi-même la fréquence des lettres.
Linux fournit une liste de mots dans le fichier /usr/share/dict/words
, j'ai donc déjà une liste de mots probables à utiliser. Le fichier words
contient beaucoup de mots que je veux, mais quelques-uns que je ne veux pas. Je voulais une liste de tous les mots qui n'étaient pas des mots composés (pas de traits d'union ni d'espaces) ou des noms propres (pas de lettres majuscules). Pour obtenir cette liste, je peux exécuter la commande grep
pour extraire uniquement les lignes composées uniquement de lettres minuscules :
$ grep '^[a-z]*$' /usr/share/dict/words
Cette expression régulière demande à grep
de faire correspondre les modèles qui ne sont que des lettres minuscules. Les caractères ^
et $
dans le modèle représentent respectivement le début et la fin de la ligne. Le regroupement [a-z]
fera correspondre uniquement les lettres minuscules a à z.
Voici un échantillon rapide du résultat :
$ grep '^[a-z]*$' /usr/share/dict/words | head
a
aa
aaa
aah
aahed
aahing
aahs
aal
aalii
aaliis
Et oui, ce sont tous des mots valables. Par exemple, « aahed » est l’exclamation au passé de « aah », comme dans la relaxation. Et un « aalii » est un arbuste tropical buissonnant.
Il ne me reste plus qu'à écrire un script gawk
pour compter les lettres de chaque mot, puis à imprimer la fréquence relative de chaque lettre trouvée.
Compter les lettres
Une façon de compter les lettres dans gawk
consiste à parcourir chaque caractère de chaque ligne d'entrée et à compter les occurrences de chaque lettre a à z. La fonction substr
renverra une sous-chaîne d'une longueur donnée, comme une seule lettre, à partir d'une chaîne plus grande. Par exemple, cet exemple de code évaluera chaque caractère c
de l'entrée :
{
len = length($0); for (i = 1; i <= len; i++) {
c = substr($0, i, 1);
}
}
Si je commence par une chaîne globale LETTERS
qui contient l'alphabet, je peux utiliser la fonction index
pour trouver l'emplacement d'une seule lettre dans l'alphabet. Je vais développer l'exemple de code gawk
pour évaluer uniquement les lettres a à z dans l'entrée :
BEGIN { LETTERS = "abcdefghijklmnopqrstuvwxyz" }
{
len = length($0); for (i = 1; i <= len; i++) {
c = substr($0, i, 1);
ltr = index(LETTERS, c);
}
}
Notez que la fonction d'index renvoie la première occurrence de la lettre de la chaîne LETTERS
, en commençant par 1 à la première lettre, ou zéro si elle n'est pas trouvée. Si j'ai un tableau de 26 éléments, je peux utiliser le tableau pour compter les occurrences de chaque lettre. Je vais ajouter ceci à mon exemple de code pour incrémenter (en utilisant ++
) le nombre de chaque lettre tel qu'il apparaît dans l'entrée :
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];
}
}
}
Fréquence relative d'impression
Une fois que le script gawk
a compté toutes les lettres, je souhaite imprimer la fréquence de chaque lettre trouvée. Je ne suis pas intéressé par le nombre total de chaque lettre de l'entrée, mais plutôt par la fréquence relative de chaque lettre. La fréquence relative met à l'échelle les comptes de sorte que la lettre avec le moins d'occurrences (telle que la lettre q) soit définie sur 1, et les autres lettres sont relatives à celle-ci.
Je vais commencer par le nombre de la lettre a, puis comparer cette valeur aux nombres de chacune des autres lettres b à z :
END {
min = count[1]; for (ltr = 2; ltr <= 26; ltr++) {
if (count[ltr] < min) {
min = count[ltr];
}
}
}
À la fin de cette boucle, la variable min
contient le nombre minimum pour n'importe quelle lettre. Je peux l'utiliser pour fournir une échelle de comptage afin d'imprimer la fréquence relative de chaque lettre. Par exemple, si la lettre avec l'occurrence la plus basse est q, alors min
sera égal au nombre q.
Ensuite, je parcours chaque lettre et je l'imprime avec sa fréquence relative. Je divise chaque nombre par min
pour imprimer la fréquence relative, ce qui signifie que la lettre avec le nombre le plus bas sera imprimée avec une fréquence relative de 1. Si une autre lettre apparaît deux fois plus souvent que le nombre le plus bas, cela la lettre aura une fréquence relative de 2. Je ne m'intéresse ici qu'aux valeurs entières, donc 2,1 et 2,9 sont identiques à 2 pour mes besoins :
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);
}
}
Mettre tous ensemble
Maintenant, j'ai un script gawk
qui peut compter la fréquence relative des lettres dans son entrée :
#!/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);
}
}
Je vais l'enregistrer dans un fichier appelé letter-freq.awk
afin de pouvoir l'utiliser plus facilement à partir de la ligne de commande.
Si vous préférez, vous pouvez également utiliser chmod +x
pour rendre le fichier exécutable seul. Le #!/usr/bin/gawk -f
sur la première ligne signifie que Linux l'exécutera en tant que script à l'aide du programme /usr/bin/gawk
. Et comme la ligne de commande gawk
utilise -f
pour indiquer quel fichier elle doit utiliser comme script, vous avez besoin de ce -f
suspendu pour que l'exécution letter-freq.awk
au niveau du shell sera correctement interprété comme exécutant /usr/bin/gawk -f letter-freq.awk
à la place.
Je peux tester le script avec quelques entrées simples. Par exemple, si j'incorpore l'alphabet dans mon script gawk
, chaque lettre doit avoir une fréquence relative de 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
Répéter cet exemple mais ajouter une instance supplémentaire de la lettre e imprimera la lettre e avec une fréquence relative de 2 et toutes les autres lettres comme 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
Et maintenant, je peux faire le grand pas ! Je vais utiliser la commande grep
avec le fichier /usr/share/dict/words
et identifier la fréquence des lettres pour tous les mots entièrement orthographiés avec des lettres minuscules :
$ 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
Parmi tous les mots minuscules du fichier /usr/share/dict/words
, les lettres j, q et x se produit le moins fréquemment. La lettre z est également assez rare. Sans surprise, la lettre e est la plus fréquemment utilisée.