script décompte de fréquence mot est trop lent
-
30-09-2019 - |
Question
Arrière-plan
A créé un script pour compter la fréquence des mots dans un fichier texte brut. Le script réalise les étapes suivantes:
- Count la fréquence des mots d'un corpus.
- Retain chaque mot dans le corpus trouvé dans un dictionnaire.
- Créer un fichier séparé par des virgules des fréquences.
Le script est à: http://pastebin.com/VAZdeKXs
#!/bin/bash
# Create a tally of all the words in the corpus.
#
echo Creating tally of word frequencies...
sed -e 's/ /\n/g' -e 's/[^a-zA-Z\n]//g' corpus.txt | \
tr [:upper:] [:lower:] | \
sort | \
uniq -c | \
sort -rn > frequency.txt
echo Creating corpus lexicon...
rm -f corpus-lexicon.txt
for i in $(awk '{if( $2 ) print $2}' frequency.txt); do
grep -m 1 ^$i\$ dictionary.txt >> corpus-lexicon.txt;
done
echo Creating lexicon...
rm -f lexicon.txt
for i in $(cat corpus-lexicon.txt); do
egrep -m 1 "^[0-9 ]* $i\$" frequency.txt | \
awk '{print $2, $1}' | \
tr ' ' ',' >> lexicon.txt;
done
Problème
Les lignes qui suivent en permanence à travers le cycle de dictionnaire pour correspondre les mots:
for i in $(awk '{if( $2 ) print $2}' frequency.txt); do
grep -m 1 ^$i\$ dictionary.txt >> corpus-lexicon.txt;
done
Il fonctionne, mais il est lent, car il scanne les mots qu'il trouve pour enlever tout ce ne sont pas dans le dictionnaire. Le code exécute cette tâche en balayant le dictionnaire pour chaque mot. (Le paramètre -m 1
arrête le balayage lorsque la correspondance est trouvée.)
Question
Comment voulez-vous optimiser le script afin que le dictionnaire ne sont pas analysés de début à la fin pour chaque mot? La plupart des mots ne sera pas dans le dictionnaire.
Merci!
La solution
Vous pouvez utiliser grep -f
pour rechercher tous les mots en un seul passage sur frequency.txt:
awk '{print $2}' frequency.txt | grep -Fxf dictionary.txt > corpus-lexicon.txt
-
-F
pour rechercher des chaînes fixes. -
-x
pour correspondre uniquement des lignes entières. -
-f
lire les motifs de recherche de dictionary.txt
En fait, vous pouvez même combiner cela avec la deuxième boucle et d'éliminer le fichier corpus lexicon.txt intermédiaire. Les deux pour les boucles peuvent être remplacés par un seul grep:
grep -Fwf dictionary.txt frequency.txt | awk '{print $2 "," $1}'
Notez que j'ai changé -x
à -w
.
Autres conseils
Ceci est généralement l'un de ces scripts que vous écririez en Perl pour la vitesse. Mais si, vous pouvez le faire tout en awk, comme moi, vous détestez les langages de programmation en écriture seule:
awk '
BEGIN {
while ((getline < "dictionary.txt") > 0)
dict[$1] = 1
}
($2 && $2 in dict) { print $2 }
' < frequency.txt > corpus-lexicon.txt
Pas besoin de la rm -f corpus-lexicon.txt
dans cette version.
Utilisez un véritable langage de programmation. Toutes les app start-ups et les analyses de fichiers vous tuent. Par exemple, voici un exemple que je viens fouetté en Python (en minimisant les lignes de code):
import sys, re
words = re.findall(r'(\w+)',open(sys.argv[1]).read())
counts = {}
for word in words:
counts[word] = counts.setdefault(word,0) + 1
open(sys.argv[2],'w').write("\n".join([w+','+str(c) for (w,c) in counts.iteritems()]))
Test d'une contre un grand fichier texte que j'avais assis aound (1.4MB, 80.000 mots selon wc), ce qui termine dans le cadre d'un second (18k mots uniques) sur un vieux PowerMac 5 ans.