web-dev-qa-db-fra.com

À partir d'un fichier, trouvez les dix mots les plus fréquents le plus efficacement possible

C'est apparemment une question d'entrevue (trouvée dans une collection de questions d'entrevue), mais même si ce n'est pas le cas, c'est plutôt cool.

On nous dit de le faire efficacement pour toutes les mesures de complexité. J'ai pensé créer un HashMap qui mappe les mots à leur fréquence. Ce serait O(n) en complexité temporelle et spatiale, mais comme il peut y avoir beaucoup de mots, nous ne pouvons pas supposer que nous pouvons tout stocker en mémoire.

Je dois ajouter que rien dans la question ne dit que les mots ne peuvent pas être stockés en mémoire, mais que se passerait-il si tel était le cas? Si ce n'est pas le cas, la question ne semble pas aussi difficile.

21
efficiencyIsBliss

Optimiser pour mon temps:

sort file | uniq -c | sort -nr | head -10

Peut-être suivi de awk '{print $2}' pour éliminer les comptes.

19
Ben Jackson

Je pense que la structure de données trie est un choix.

Dans ce dernier, vous pouvez enregistrer le nombre de mots dans chaque nœud représentant la fréquence de Word composée de caractères sur le chemin allant de la racine au nœud actuel.

La complexité temporelle pour configurer le fichier est O(Ln) ~ O(n) (où L est le nombre de caractères du mot le plus long, que nous pouvons traiter comme une constante). Pour trouver le top 10 des mots, nous pouvons parcourir le trie, qui coûte aussi O (n). Donc, il faut O(n) pour résoudre ce problème.

12

Une solution complète ressemblerait à ceci:

  1. Effectuer un tri externe O (N log N)
  2. Compter le freq Word dans le fichier O (N)
  3. (Une alternative serait l'utilisation d'un Trie comme @Summer_More_More_Tea pour compter les fréquences, si vous pouvez vous permettre cette mémoire.) O (k * N) // pour les deux premières étapes
  4. Utilisez un min-tas:
    • Placez les n premiers éléments sur le tas
    • Pour chaque mot restant, ajoutez-le au tas et supprimez le nouveau min dans le tas
    • En fin de compte, le tas contiendra les n-ième mots les plus communs O (| words | * log (n))

Avec le Trie, le coût serait de O (k * N), car le nombre total de mots est généralement supérieur à la taille du vocabulaire. Enfin, puisque k est plus petit pour la plupart des langues occidentales, vous pouvez supposer une complexité linéaire.

3
Alessandro

Disons que nous attribuons un nombre premier aléatoire à chacun des 26 alphabets. Ensuite, nous numérisons le fichier. Chaque fois que nous trouvons un mot, nous calculons sa valeur de hachage (formule basée sur la position et la valeur des alphabets constituant le mot). Si nous trouvons cette valeur dans la table de hachage, nous savons avec certitude que nous ne la rencontrons pas pour la première fois et nous incrémentons sa valeur de clé. Et maintenez un tableau de 10 maximum. Mais si nous rencontrons un nouveau hachage, nous stockons le pointeur de fichier pour cette valeur de hachage et initialisons la clé à 0.

2
amol_beast

J'ai fait en C # comme ceci (un échantillon)

int wordFrequency = 10;
string words = "hello how r u u u u  u  u u  u  u u u  u u u u  u u u ? hello there u u u u ! great to c u there. hello .hello hello hello hello hello .hello hello hello hello hello hello ";            

var result = (from Word in words.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries)
                          group Word by Word into g
                          select new { Word = g.Key, Occurance = g.Count() }).ToList().FindAll(i => i.Occurance >= wordFrequency);
2
user372724

Vous pouvez faire un compromis temps/espace et utiliser O(n^2) pour le temps et O(1) pour l'espace (mémoire) en comptant le nombre de fois qu'un mot se produit chaque fois que vous le rencontrez dans un passage linéaire des données. Si le nombre est supérieur au top 10 trouvé jusqu'à présent, conservez le mot et le nombre, sinon ignorez-le.

1
user470379

Il est préférable de créer un hachage et de trier les valeurs. Je suis enclin à accepter. _ { http://www.allinterview.com/showanswers/56657.html } _

Voici une implémentation de Bash qui fait quelque chose de similaire ... Je pense que http://www.commandlinefu.com/commands/view/5994/computes-the-most-more-frequent-used-words-of-a--text) -fichier

1
EnabrenTane

En fonction de la taille des données d'entrée, il peut s'avérer judicieux ou non de conserver un HashMap. Disons par exemple, notre carte de hachage est trop grande pour tenir dans la mémoire principale. Cela peut entraîner un très grand nombre de transferts de mémoire car la plupart des implémentations de hash-map ont besoin d'un accès aléatoire et ne seraient pas très performantes dans le cache.

Dans de tels cas, le tri des données d'entrée constituerait une meilleure solution.

1
Sanjit Saluja

Je pense que ceci est une application typique de la sorte de comptage puisque la somme des occurrences de chaque mot est égale au nombre total de mots. Une table de hachage avec une sorte de comptage devrait faire le travail dans un temps proportionnel au nombre de mots.

1
Aly Farahat

step 1 : Si le fichier est très volumineux et ne peut pas être trié en mémoire, vous pouvez le scinder en morceaux pouvant être triés en mémoire.

step 2 : Pour chaque bloc trié, calculez les paires triées de (mots, nr_occurrence). Vous pouvez alors renoncer aux morceaux car vous n'avez besoin que des paires triées.

step 3 : Parcourez les morceaux, triez-les et gardez toujours les dix meilleurs résultats.

Exemple:

Étape 1 :

a b a ab abb a a b b c c ab ab

divisé en :

morceau 1: a b a ab
partie 2: abb a a b b
morceau 3: c c ab ab ab

Étape 2 :

morceau 1: a2, b1, ab1 morceau 2: a2, b2, abb1
morceau 3: c2, ab2

Étape 3 (fusionnez les morceaux et gardez les dix premières apparitions):

a4 b3 ab3 c2 abb1

0
raluca

Un arbre Radix ou l'une de ses variantes vous permettra généralement d'économiser de l'espace de stockage en réduisant les séquences courantes.
Pour le construire, il faudra O(nk) - où k est "la longueur maximale de toutes les chaînes de l'ensemble".

0
    int k = 0;
    int n = i;
    int j;
    string[] stringList = h.Split(" ".ToCharArray(),
                                  StringSplitOptions.RemoveEmptyEntries);
    int m = stringList.Count();
    for (j = 0; j < m; j++)
    {
        int c = 0;
        for (k = 0; k < m; k++)
        {
            if (string.Compare(stringList[j], stringList[k]) == 0)
            {
                c = c + 1;
            }
        }
    }
0
Amit Bose

Parcourez la chaîne de mots et stockez-les dans un dictionnaire (avec python) et le nombre de fois où ils apparaissent en tant que valeur.

0
a sandwhich

Ce n’est pas le processeur le plus efficace, ni le moins du monde, mais il n’a fallu que 2 minutes pour en découdre:

Perl -lane '$h{$_}++ for @F; END{for $w (sort {$h{$b}<=>$h{$a}} keys %h) {print "$h{$w}\t$w"}}' file | head

Boucle sur chaque ligne avec -n
Fractionner chaque ligne en @F mots avec -a
Chaque $_ mot incrémente le hachage %h
Une fois la END de file atteinte,
sort le hachage par la fréquence
Imprimer la fréquence $h{$w} et le mot $w
Utilisez bash head pour vous arrêter à 10 lignes

En utilisant le texte de cette page Web comme entrée:

121     the
77      a
48      in
46      to
44      of
39      at
33      is
30      vote
29      and
25      you

J'ai comparé cette solution à la solution Shell la mieux cotée (ben jackson) sur un fichier texte de 3,3 Go contenant 580 000 000 mots.
Perl 5.22 s’est achevé en 171 secondes, tandis que la solution Shell s’est terminée en 474 secondes.

0
Chris Koknat

Si la liste de mots ne tient pas dans la mémoire, vous pouvez fractionner le fichier jusqu'à ce qu'il le soit. Générez un histogramme de chaque partie (de manière séquentielle ou parallèle) et fusionnez les résultats (les détails peuvent être un peu délicats si vous voulez une exactitude garantie pour toutes les entrées, mais ne compromettez pas le O(n) effort, ou le temps O(n/k) pour k tâches).

0
comingstorm