117 votes

Algorithme pour trouver le top 10 des termes de recherche

Je suis actuellement en train de préparer pour une entrevue, et cela me rappelle une question que m'a demandé une fois dans une précédente interview qui disait quelque chose comme ceci:

"Il vous a été demandé de concevoir un logiciel pour afficher en permanence le top 10 des termes de recherche sur Google. Vous avez accès à une alimentation qui fournit une interminable de flux en temps réel des termes de recherche actuellement en cours de recherche sur Google. Décrire ce que l'algorithme et structures de données vous pouvez utiliser pour mettre en œuvre cette. Vous êtes à la conception de deux variantes:

(i) Afficher les 10 premiers termes de recherche de tous les temps (c'est à dire depuis que vous avez commencé la lecture de l'alimentation).

(ii) d'Afficher uniquement le top 10 des termes de recherche pour le mois passé, mis à jour toutes les heures.

Vous pouvez utiliser une approximation pour obtenir la liste du top 10, mais vous devez justifier votre choix."
J'ai bombardé dans cette interview et encore ont vraiment aucune idée de comment le mettre en œuvre.

La première partie demande pour les 10 les plus fréquentes des éléments dans un développement continu de la sous-séquence d'une liste infinie. J'ai regardé dans les algorithmes de sélection, mais ne pouvais pas trouver toutes les versions en ligne pour résoudre ce problème.

La deuxième partie utilise une liste restreinte, mais en raison de la grande quantité de données en cours de traitement, vous ne pouvez pas vraiment de magasin sur l'ensemble du mois de termes de recherche dans la mémoire et de calculer un histogramme de chaque heure.

Le problème est rendu plus difficile par le fait que le top 10 liste est continuellement mise à jour, donc, en quelque sorte, vous devez être le calcul de votre top 10 sur une fenêtre glissante.

Des idées?

55voto

erickson Points 127945

Si exact résultats ont été nécessaires, il faudrait beaucoup de stockage, et il serait assez lente. Si le rapprochement est autorisé, l'algorithme suivant peut-être partie de la solution qui est efficace dans le temps et dans l'espace.

Commencez avec une carte vide (rouge-noir de l'arbre). Les clés seront les termes de recherche, et les valeurs seront un compteur pour le terme.

  1. Examinez chaque élément dans le flux.
  2. Si le terme existe dans la carte, incrémenter les associés compteur.
  3. Sinon, si la carte a moins de candidats que vous êtes à la recherche pour, l'ajouter à la carte avec un nombre d'une.
  4. Cependant, si la carte est "plein", décrémenter le compteur à chaque entrée. Si un compteur atteint zéro au cours de ce processus, le retirer de la carte.

Ce processus permettra de déterminer terme de recherche qui se produit avec une fréquence spécifiée. Par exemple, si vous limitez la carte à 99 entrées, vous êtes assuré de trouver tout terme qui se produit plus de 1/(1 + 99) (1%) de temps en temps. Ceci est différent de la recherche la plus fréquemment des éléments. Par exemple, si le premier élément se produit seulement 0,5% du temps, vous manquerez sauf si vous définissez le seuil inférieur. Notez également que les valeurs finales des compteurs ne signifie pas grand-chose. Le plus commun élément dans le flux peut se retrouver avec le plus bas du compteur.

Notez que vous pouvez traiter une quantité infinie de données avec un montant fixe de stockage (juste la taille fixe de la carte). La quantité de stockage requise dépend uniquement du seuil d'intérêt, et la taille du flux n'a pas d'importance.

Peut-être vous avez de la mémoire tampon d'une heure de recherches, et d'effectuer cette procédure sur l'heure de données. Si vous pouvez prendre une seconde pour passer au-dessus de cette heure du journal de recherche, vous pouvez obtenir un nombre exact d'occurrences des meilleurs "candidats" identifiés lors de la première passe.

Les candidats qui dépassent le seuil d'intérêt sont enregistrés comme un résumé. Garder l'équivalent d'un mois de ces résumés, de jeter le plus ancien de chaque heure, et vous devriez avoir une bonne approximation de la plus commune des termes de recherche.


Un Long Exemple

Nous allons marcher à travers une application de l'algorithme où nous sommes à la recherche pour l'un quelconque des termes qui se produisent plus que 1% du temps. Disons que le flux de données contient 10à 12 éléments, et juste pour le plaisir, que le premier 1010 + 1 éléments sont "foo", et le reste du flux comprend distinctes des mots, des termes qui apparaissent une seule fois.

Le flux de longueur n'a pas d'importance; seule notre seuil. De trouver quelque chose qui se produit de plus de 1% du temps, nous avons besoin de suivre seulement 99 éléments.

Après la première 1010 + 1 éléments, notre suivi contient "toto" avec un décompte de 1010 + 1.

La prochaine 98 éléments sont ajoutés pour le suivi de l'ensemble avec un nombre de 1, parce que le jeu n'est pas "complet".

Ensuite, l'élément suivant (la 99e non"foo" terme) n'a jamais été vu avant, mais l'ensemble est complet, de sorte que tous les compteurs sont décrémenté de 1. Que signifie "foo" a un nombre de 10de 10, et les autres 98 éléments sont réduits à zéro. Parce qu'ils sont réduits à zéro, ils sont supprimés à partir du suivi.

Le schéma se répète, avec la prochaine 98 termes ajoutés pour le suivi, pour être enlevés par la 99e, qui a également décrémente le "foo" le compteur de un.

Vous pouvez voir que le compteur de "foo" est réduit à une fois tous les 99 éléments, soit un total de 1010 - 1 fois avant la fin du flux est atteinte. À la fin du flux, le nombre de "foo" est toujours 2, et il est maintenu comme un candidat qui a dépassé le seuil.

Je tiens également à noter que le suivi de l'ensemble contient également de 98 "junk" éléments avec un compteur de 1. Les valeurs du compteur sont hors de propos. Afin de déterminer quelle est la fréquence de ces termes sont d'un second passage sur le ruisseau est nécessaire.

48voto

Dimitris Andreou Points 5398

Eh bien, ressemble à un tas de données, avec peut-être un coût prohibitif pour stocker toutes les fréquences. Lorsque la quantité de données est si grande que nous ne pouvons pas espérer pour stocker l'ensemble, nous entrons dans le domaine de flux de données algorithmes.

Livre utile dans ce domaine: Muthukrishnan - "Flux de Données: Algorithmes et Applications"

Étroitement liée référence à un problème qui j'ai pris de la ci-dessus: Manku, Motwani - "Fréquence Approximative Compte plus de Flux de Données" [pdf]

Par la voie, Motwani, de Stanford, (edit) était un auteur de la très importante "Algorithmes Randomisés" livre. Le 11e chapitre de ce livre traite de ce problème. Edit: Désolé, mauvaise référence, ce chapitre est un problème différent. Après vérification, j'ai plutôt recommander l'article 5.1.2 de Muthukrishnan du livre, disponible en ligne.

Heh, nice questions de l'entrevue.

19voto

SiLent SoNG Points 1510

C'est l'un des projet de recherche que je suis au courant. L'exigence est presque exactement comme le vôtre, et nous avons développé de nice algorithmes pour résoudre le problème.

L'Entrée

L'entrée est un flux sans fin de l'anglais des mots ou des phrases (nous nous référons comme tokens).

La Sortie

  1. Sortie top N jetons que nous avons vu jusqu' loin de tous les jetons que nous avons vu!)
  2. Sortie top N jetons dans un historique de la fenêtre, disons, dernier jour ou la semaine dernière.

Une application de cette recherche est de trouver le sujet d'actualité et les tendances de sujet dans Twitter ou Facebook. Nous avons un reptile qui rampe sur le site web, qui génère un flux de mots, ce qui permettra d'alimenter le système. Ensuite, le système affichera les mots ou les phrases de fréquence supérieure soit globale ou historique. Imaginez dans les deux dernières semaines, le membre de phrase "Coupe du Monde" seraient apparaît de nombreuses fois dans Twitter. Ne sorte de "Paul le poulpe". :)

Chaîne de caractères en nombres Entiers

Le système a un nombre entier ID pour chaque mot. Bien qu'il est presque infini de mots possibles sur Internet, mais après avoir accumulé un grand nombre de mots, la possibilité de trouver de nouveaux mots devient de plus en plus bas. Nous avons déjà trouvé 4 millions de mots différents, et d'attribuer un IDENTIFIANT unique pour chaque. Cet ensemble de données peut être chargé dans la mémoire comme une table de hachage, consommant environ 300MO de mémoire. (Nous avons mis en place notre propre table de hachage. La Java de la mise en œuvre prend énorme surcharge de la mémoire)

Chaque phrase peut alors être identifié comme un tableau d'entiers.

Ceci est important, car de tri et de comparaisons sur des entiers est beaucoup plus rapide que sur les cordes.

L'Archivage De Données

Le système permet d'archiver des données pour chaque jeton. En gros, c'est des paires d' (Token, Frequency). Toutefois, la table qui stocke les données seraient énormes, tels que nous avons à la partition de la table physiquement. Une fois que la partition système est fondé sur ngrams du jeton. Si le jeton est un seul mot, il est 1gram. Si le jeton est de deux mots de la phrase, il est 2gram. Et ce qui se passe. À peu près à 4gram nous avons 1 milliard d'enregistrements, avec une table de taille moyenne autour de 60 GO.

Le Traitement Des Flux Entrants

Le système absorbe entrant phrases jusqu'à ce que la mémoire devient pleinement utilisé (Ya, nous avons besoin d'un MemoryManager). Après la prise de la N des phrases et de la stocker dans la mémoire, le système s'arrête, et commence à marquer chaque phrase en mots et en phrases. Chaque jeton (mot ou phrase) est compté.

Pour les très fréquentes jetons, ils sont toujours gardés en mémoire. Pour les moins fréquentes jetons, ils sont triés en fonction Id (souvenez-vous de nous traduire la Chaîne dans un tableau de nombres entiers), et sérialisée dans un fichier sur disque.

(Cependant, pour votre problème, puisque vous comptez uniquement sur les mots, alors vous pouvez mettre tous les mot-fréquence de la carte dans la mémoire. Soigneusement conçu la structure de données de la consommer seulement 300 MO de mémoire pour 4 millions de mots différents. Un indice: utiliser des caractères ASCII dans le fichier pour représenter des Chaînes de caractères), et c'est tout à fait acceptable.

Pendant ce temps, il y aura un autre processus qui est activé une fois qu'il trouve un fichier de disque généré par le système, puis commencer à fusionner. Depuis le disque fichier est trié, la fusion prendrait un processus similaire comme la fusion de tri. Certains de conception doivent être pris en compte ici, car nous voulons éviter de trop aléatoire du disque cherche. L'idée est d'éviter de lire (processus de fusion)/write (sortie du système) en même temps, et de laisser le processus de fusion de lire un disque lors de l'écriture sur un disque différent. C'est comme pour la mise en œuvre d'un verrouillage.

En fin de Journée

À la fin de la journée, le système aura beaucoup de fréquentes jetons avec la fréquence enregistrée dans la mémoire, et beaucoup d'autres moins fréquentes jetons stockées dans plusieurs fichiers de disque (et chaque fichier est trié).

La purge du système de la carte mémoire dans un fichier de disque (tri). Maintenant, le problème devient de la fusion d'un ensemble de triés fichier de disque. A l'aide du même processus, on obtient une triés fichier de disque à la fin.

Ensuite, la tâche finale consiste à fusionner la triées disque fichier dans l'archive de la base de données. Dépend de la taille de l'archive de la base de données, l'algorithme fonctionne comme ci-dessous si elle est assez grande:

   for each record in sorted disk file
        update archive database by increasing frequency
        if rowcount == 0 then put the record into a list
   end for

   for each record in the list of having rowcount == 0
        insert into archive database
   end for

L'intuition est que, après un certain temps, le nombre d'insertion va devenir de plus en plus petites. De plus en plus et de l'opération de mise à jour uniquement. Et cette mise à jour ne sera pas pénalisé par l'index.

Espérons que toute cette explication pourrait l'aider. :)

4voto

IVlad Points 20932

Vous pouvez utiliser une table de hachage combiné avec un arbre de recherche binaire. Mettre en œuvre un <search term, count> dictionnaire qui vous indique le nombre de fois que chaque terme de recherche a été recherché.

Évidemment une itération à l'ensemble de la table de hachage de chaque heure, le top 10 est très mauvais. Mais c'est google nous parlons, de sorte que vous pouvez supposer que le top dix obtiendrez tout, disons plus de 10 000 visites (c'est probablement un nombre beaucoup plus grand bien). Donc chaque fois qu'un terme de recherche count est supérieur à 10 000, de l'insérer dans la BST. Puis toutes les heures, vous n'avez qu'à obtenir le premier 10 de la BST, qui devrait contenir relativement peu d'entrées.

Cela résout le problème de la top 10 de tous les temps.


La partie vraiment difficile est de traiter avec un terme de prendre une autre place dans le rapport mensuel (par exemple, "stack overflow" peut avoir les 50 000 visites au cours des deux derniers mois, mais seulement 10 000 le mois dernier, tandis que "amazon" peut avoir les 40 000 pour les deux derniers mois, mais 30 000 pour le mois passé. Vous voulez "amazon" avant de "stack overflow" dans votre rapport mensuel). Pour ce faire, je voudrais stocker, pour tous les grands (plus de 10 000 toutes les recherches en temps) des termes de recherche, une liste de 30 jours qui vous indique le nombre de fois que le terme a été recherché pour chaque jour. La liste pourrait fonctionner comme une file d'attente FIFO: vous supprimez le premier jour et insérer un nouveau chaque jour (ou chaque heure, mais vous pourriez avoir besoin pour stocker davantage d'informations, ce qui signifie plus de mémoire / de l'espace. Si la mémoire n'est pas un problème de le faire, sinon rendez-vous pour que ce "rapprochement" ils parlent).

Cela ressemble à un bon début. Vous pouvez ensuite vous soucier de l'élagage dans les termes qui ont plus de 10 000 visites mais je n'ai pas eu beaucoup depuis longtemps, et des trucs comme ça.

3voto

Cam Points 6835

cas i)

Maintenir une table de hachage pour tous les searchterms, ainsi qu'un classement parmi les dix premiers de la liste distincte de la table de hachage. Chaque fois qu'une recherche se produit, l'incrément de l'élément approprié dans la table de hachage et de vérifier pour voir si l'élément en question doit maintenant être activée avec le 10ème élément en haut de la liste des dix.

O(1) recherche pour le top-ten de la liste, et max O(log(n)) à une insertion dans la table de hachage (en supposant que les collisions géré par un auto-équilibrage arbre binaire).

cas ii) Au lieu de maintenir un énorme table de hachage et une petite liste, nous maintenons une table de hachage et une liste triée de tous les éléments. Chaque fois qu'une recherche est effectuée, ce terme est incrémenté dans la table de hachage, et dans la liste triée, le terme peut être vérifié pour voir si elle devrait passer avec le terme qui suit. Un auto-équilibrage arbre binaire pourrait fonctionnent bien pour cela, que nous devons également être en mesure d'interroger rapidement (plus sur cela plus tard).

En outre, nous maintenons également une liste des "heures" sous la forme d'une liste FIFO (file d'attente). Chaque 'heure' élément doit contenir une liste de toutes les recherches effectuées au sein de cette heure. Ainsi, par exemple, la liste de nos heures pourrait ressembler à ceci:

Time: 0 hours
      -Search Terms:
          -free stuff: 56
          -funny pics: 321
          -stackoverflow: 1234
Time: 1 hour
      -Search Terms:
          -ebay: 12
          -funny pics: 1
          -stackoverflow: 522
          -BP sucks: 92

Ensuite, à chaque heure: Si la liste contient au moins 720 heures (c'est le nombre d'heures dans les 30 jours), regarde le premier élément dans la liste, et pour chaque terme de recherche, de décrémentation de cet élément dans la table de hachage par le montant approprié. Ensuite, supprimez cette première heure de l'élément de la liste.

Donc, disons que nous en sommes à l'heure 721, et nous sommes prêts à regarder la première heure dans notre liste (ci-dessus). Nous avions décrémenter des trucs gratuits par 56 dans la table de hachage, de drôles de photos par 321, etc., puis retirez l'heure 0 à partir de la liste complètement puisque nous n'aurez plus jamais besoin de le regarder de nouveau.

La raison pour laquelle nous maintenir une liste triée de tous les termes qui permet d'obtenir rapidement des requêtes est parce que toutes les heures que nous passons à travers les termes de recherche à partir de 720 heures, nous devons nous assurer que le top-ten reste de liste triée. Si, comme nous l'décrémenter 'trucs' de 56 dans la table de hachage par exemple, nous aimerions vérifier pour voir où elle appartient maintenant dans la liste. Parce que c'est un auto-équilibrage arbre binaire, tout cela peut être accompli bien en O(log(n)) de temps.


Edit: autant Sacrifier la précision de l'espace...

Il pourrait être utile de mettre en œuvre un grand liste dans le premier comme dans le second. Nous pourrions appliquer la suite de l'optimisation de l'espace sur les deux cas: Exécuter une tâche cron pour supprimer tous, mais le top x des éléments dans la liste. Cela permettrait de limiter les besoins d'espace en bas (et donc faire des requêtes sur la liste des plus rapide). Bien sûr, il en résulterait un résultat approximatif, mais c'est autorisé. x peut être calculé avant le déploiement de l'application en fonction de la mémoire disponible, et de régler dynamiquement si plus de mémoire devient disponible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X