Recherche de site Web

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.

Articles connexes: