Algorithme pour trouver les 10 principaux termes de recherche


115

Je me prépare actuellement pour une interview, et cela m'a rappelé une question qui m'a été posée une fois lors d'une interview précédente qui ressemblait à ceci:

"On vous a demandé de concevoir un logiciel pour afficher en permanence les 10 principaux termes de recherche sur Google. Vous avez accès à un flux qui fournit un flux sans fin en temps réel de termes de recherche actuellement recherchés sur Google. Décrivez quels algorithmes et structures de données vous utiliseriez pour l'implémenter. Vous devez concevoir deux variantes:

(i) Affichez les 10 principaux termes de recherche de tous les temps (c'est-à-dire depuis que vous avez commencé à lire le flux).

(ii) N'afficher que les 10 principaux termes de recherche du mois dernier, mis à jour toutes les heures.

Vous pouvez utiliser une approximation pour obtenir la liste des 10 premiers, mais vous devez justifier vos choix. »
J'ai bombardé dans cette interview et je ne sais toujours pas vraiment comment mettre en œuvre cela.

La première partie demande les 10 éléments les plus fréquents dans une sous-séquence en croissance continue d'une liste infinie. J'ai examiné les algorithmes de sélection, mais je n'ai trouvé aucune version en ligne pour résoudre ce problème.

La deuxième partie utilise une liste finie, mais en raison de la grande quantité de données en cours de traitement, vous ne pouvez pas vraiment stocker tout le mois de termes de recherche en mémoire et calculer un histogramme toutes les heures.

Le problème est rendu plus difficile par le fait que la liste des 10 premiers est constamment mise à jour, vous devez donc calculer votre top 10 sur une fenêtre glissante.

Des idées?


11
@BlueRaja - Ce n'est pas une question d'entrevue stupide, c'est une mauvaise interprétation de la part de l'OP. Il ne demande pas les éléments les plus fréquents dans une liste infinie, mais les éléments les plus fréquents d'une sous-séquence finie d'une liste infinie. Pour continuer votre analogie,what is the most frequent item in the subsequence [2; 2; 3; 3; 3; 4; 4; 4; 4; 5; 5] of your sequence?
IVlad

3
@BlueRaja - C'est certainement une question difficile, mais je ne vois pas pourquoi c'est stupide - cela semble représentatif d'un problème assez typique auquel sont confrontées les entreprises disposant d'énormes ensembles de données. @IVlad - Correction du problème selon votre suggestion, mauvaise formulation de ma part!
del

Réponses:


47

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

Livre utile dans ce domaine: Muthukrishnan - "Flux de données: algorithmes et applications"

Référence étroitement liée au problème actuel que j'ai choisi parmi les éléments ci-dessus: Manku, Motwani - "Approximate Frequency Counts over Data Streams" [pdf]

À propos, Motwani, de Stanford, (modifier) ​​était un auteur du très important livre "Algorithmes aléatoires" . Le 11e chapitre de ce livre traite de ce problème . Edit: Désolé, mauvaise référence, ce chapitre particulier est sur un problème différent. Après vérification, je recommande plutôt la section 5.1.2 du livre de Muthukrishnan , disponible en ligne.

Heh, belle question d'entretien.


2
+1 Trucs très intéressants, il devrait y avoir un moyen sur les sites de taguer des trucs "à lire". Merci d'avoir partagé.
Ramadheer Singh

@Gollum: j'ai un dossier à lire dans mes signets; vous pouvez simplement le faire. Je sais que ces liens sont ajoutés au mien :)
Cam

+1. Les algorithmes de streaming sont exactement le sujet ici, et le livre de Muthu (le seul livre écrit à ce jour, AFAIK) est génial.
ShreevatsaR

1
+1. Connexes: en.wikipedia.org/wiki/Online_algorithm . BTW, Motwani est mort récemment, alors peut - être était un auteur est plus précis.

Très étrange. Je le connaissais par le livre, mais il devait sûrement être plus célèbre pour cette raison: "Motwani était l'un des co-auteurs (avec Larry Page et Sergey Brin, et Terry Winograd) d'un premier article influent sur l'algorithme PageRank, la base des techniques de recherche de Google. "( en.wikipedia.org/wiki/Rajeev_Motwani )
Dimitris Andreou

55

Aperçu de l'estimation de fréquence

Il existe des algorithmes bien connus qui peuvent fournir des estimations de fréquence pour un tel flux en utilisant une quantité de stockage fixe. L'un est Fréquent, par Misra et Gries (1982). À partir d'une liste de n éléments, il trouve tous les éléments qui se produisent plus de n / k fois, en utilisant des compteurs k - 1 . Ceci est une généralisation de Boyer et Moore majorité algorithme (Fischer-Salzberg, 1982), où k est 2. Manku et de Motwani de LossyCounting (2002) et de Metwally de peu encombrant algorithmes (2005) ont des exigences d'espace similaires, mais peuvent fournir des estimations plus précises sous certaines conditions.

La chose importante à retenir est que ces algorithmes ne peuvent fournir que des estimations de fréquence. Plus précisément, l'estimation de Misra-Gries peut sous-estimer la fréquence réelle de (n / k) éléments.

Supposons que vous disposiez d'un algorithme capable d'identifier positivement un élément uniquement s'il se produit plus de 50% du temps. Alimentez cet algorithme un flux de N éléments distincts, puis ajoutez N - 1 copies supplémentaires d'un élément, x , pour un total de 2N - 1 éléments. Si l'algorithme vous indique que x dépasse 50% du total, il doit avoir été dans le premier flux; si ce n'est pas le cas, x n'était pas dans le flux initial. Pour que l'algorithme puisse effectuer cette détermination, il doit stocker le flux initial (ou un résumé proportionnel à sa longueur)! Ainsi, nous pouvons nous prouver que l'espace requis par un tel algorithme "exact" serait Ω ( N ).

Au lieu de cela, ces algorithmes de fréquence décrits ici fournissent une estimation, identifiant tout élément qui dépasse le seuil, ainsi que certains éléments qui tombent en dessous de celui-ci d'une certaine marge. Par exemple, l' algorithme Majority , utilisant un seul compteur, donnera toujours un résultat; si un élément dépasse 50% du flux, il sera trouvé. Mais cela peut également vous donner un élément qui n'apparaît qu'une seule fois. Vous ne le sauriez pas sans faire un deuxième passage sur les données (en utilisant, encore une fois, un seul compteur, mais en ne recherchant que cet élément).

L'algorithme fréquent

Voici une description simple de l' algorithme Frequent de Misra-Gries . Demaine (2002) et d'autres ont optimisé l'algorithme, mais cela vous donne l'essentiel.

Spécifiez la fraction seuil, 1 / k ; tout élément qui se produit plus de n / k fois sera trouvé. Créez une carte vide (comme un arbre rouge-noir); les clés seront des termes de recherche et les valeurs seront un compteur pour ce terme.

  1. Regardez chaque élément du flux.
  2. Si le terme existe dans la carte, incrémentez le compteur associé.
  3. Sinon, si la carte contient moins de k - 1 entrées, ajoutez le terme à la carte avec un compteur de un.
  4. Cependant, si la carte a déjà k - 1 entrées, décrémentez le compteur à chaque entrée. Si un compteur atteint zéro pendant ce processus, supprimez-le de la carte.

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

Comptage des recherches

Dans ce contexte, vous pouvez peut-être tamponner une heure de recherche et effectuer ce processus sur les données de cette heure. Si vous pouvez effectuer un deuxième passage sur le journal de recherche de cette heure, vous pouvez obtenir un décompte exact des occurrences des meilleurs "candidats" identifiés lors du premier passage. Ou, peut-être qu'il est normal de faire un seul passage et de signaler tous les candidats, sachant que tout élément qui devrait être là est inclus, et que tous les extras ne sont que du bruit qui disparaîtra dans l'heure qui suit.

Tous les candidats qui dépassent vraiment le seuil d'intérêt sont stockés sous forme de résumé. Conservez la valeur d'un mois de ces résumés, en jetant le plus ancien chaque heure, et vous obtiendrez une bonne approximation des termes de recherche les plus courants.


Je pense que cette solution peut agir comme un filtre, réduisant le nombre de termes de recherche qui vous intéressent. Si un terme fait partie de la carte, commencez à suivre ses statistiques réelles, même s'il ne figure pas sur la carte. Vous pouvez ensuite sauter le deuxième passage sur les données et produire un top 10 trié à partir des statistiques limitées que vous avez collectées.
Dolph

J'aime la manière élégante d'élaguer les termes les moins recherchés de l'arbre en décrémentant les compteurs. Mais une fois que la carte est "pleine", cela ne nécessitera-t-il pas une étape de décrémentation pour chaque nouveau terme de recherche qui arrive? Et une fois que cela commence à se produire, cela n'entraînera-t-il pas la suppression rapide des nouveaux termes de recherche de la carte avant qu'ils n'aient la possibilité que leurs compteurs augmentent suffisamment?
du

1
@del - Gardez à l'esprit que cet algorithme sert à localiser les termes qui dépassent une fréquence seuil spécifiée, pas nécessairement à trouver les termes les plus courants. Si les termes les plus courants tombent en dessous du seuil spécifié, ils ne seront généralement pas trouvés. Votre préoccupation concernant la suppression de termes plus récents "trop ​​rapidement" pourrait être associée à ce cas. Une façon de voir cela est qu'il y a de vrais "signaux" de popularité, ils se démarqueront sensiblement du "bruit". Mais parfois, il n'y a aucun signal à trouver, juste une recherche aléatoire statique.
erickson

@erickson - Exactement - ce à quoi je veux en venir, c'est que l'hypothèse avec cet algorithme est que les 10 premiers mots sont uniformément répartis dans la fenêtre de mesure. Mais tant que vous gardez la fenêtre de mesure suffisamment petite (par exemple 1 heure), ce serait probablement une hypothèse valable.
del

1
@erickson, alors qu'une distribution uniforme n'est pas une exigence, je me demande comment cela fonctionnerait dans une distribution plus réaliste (power-law, Zipf). Supposons que nous ayons N mots distincts, et gardons l'arbre rouge-noir de capacité K, en espérant qu'il aboutira aux K termes les plus fréquents. Si la fréquence cumulée des termes de (N - K) mots est plus grande que la fréquence cumulée des K mots les plus fréquents, l'arbre à la fin est garanti pour contenir des déchets. Êtes-vous d'accord?
Dimitris Andreou

19

C'est l'un des projets de recherche que je vis actuellement. L'exigence est presque exactement la vôtre, et nous avons développé de beaux algorithmes pour résoudre le problème.

L'entrée

L'entrée est un flux infini de mots ou de phrases anglais (nous les appelons tokens).

Le résultat

  1. Sortez les N premiers jetons que nous avons vus jusqu'à présent (de tous les jetons que nous avons vus!)
  2. Sortez les N premiers jetons dans une fenêtre historique, par exemple, le dernier jour ou la semaine dernière.

Une application de cette recherche est de trouver le sujet brûlant ou les tendances du sujet sur Twitter ou Facebook. Nous avons un robot d'exploration qui parcourt le site Web, ce qui génère un flux de mots, qui alimentera le système. Le système sortira alors les mots ou les phrases de fréquence la plus élevée, soit globalement, soit historiquement. Imaginez au cours des dernières semaines que l'expression «Coupe du monde» apparaîtra plusieurs fois sur Twitter. Il en va de même pour "Paul le poulpe". :)

Chaîne en nombres entiers

Le système a un identifiant entier pour chaque mot. Bien qu'il existe une infinité 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 faible. Nous avons déjà trouvé 4 millions de mots différents et attribué un identifiant unique à chacun. Cet ensemble complet de données peut être chargé en mémoire sous forme de table de hachage, consommant environ 300 Mo de mémoire. (Nous avons implémenté notre propre table de hachage. L'implémentation de Java prend une énorme surcharge de mémoire)

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

Ceci est important, car le tri et les comparaisons sur les entiers sont beaucoup plus rapides que sur les chaînes.

Archiver les données

Le système conserve les données d'archive pour chaque jeton. Fondamentalement, ce sont des paires de (Token, Frequency). Cependant, la table qui stocke les données serait si énorme que nous devions partitionner la table physiquement. Une fois que le schéma de partition est basé sur les ngrammes du jeton. Si le jeton est un seul mot, c'est 1 gramme. Si le jeton est une phrase de deux mots, il s'agit de 2 grammes. Et cela continue. Environ à 4 grammes, nous avons 1 milliard d'enregistrements, avec une table d'environ 60 Go.

Traitement des flux entrants

Le système absorbe les phrases entrantes jusqu'à ce que la mémoire soit pleinement utilisée (oui, nous avons besoin d'un MemoryManager). Après avoir pris N phrases et enregistré en mémoire, le système s'arrête et commence à convertir chaque phrase en mots et en phrases. Chaque jeton (mot ou phrase) est compté.

Pour les jetons très fréquents, ils sont toujours conservés en mémoire. Pour les jetons moins fréquents, ils sont triés en fonction des ID (rappelez-vous que nous traduisons la chaîne en un tableau d'entiers) et sérialisés dans un fichier disque.

(Cependant, pour votre problème, puisque vous ne comptez que des mots, vous pouvez mettre tous les mappages de fréquences de mots en mémoire uniquement. Une structure de données soigneusement conçue ne consommerait que 300 Mo de mémoire pour 4 millions de mots différents. Un conseil: utilisez le caractère ASCII pour représentent des chaînes), ce qui est tout à fait acceptable.

En attendant, il y aura un autre processus qui sera activé une fois qu'il trouvera un fichier disque généré par le système, puis commencera à le fusionner. Puisque le fichier disque est trié, la fusion prendrait un processus similaire à celui du tri par fusion. Certaines conceptions doivent également être prises en compte ici, car nous voulons éviter trop de recherches aléatoires sur les disques. L'idée est d'éviter la lecture (processus de fusion) / écriture (sortie système) en même temps et de laisser le processus de fusion lire un disque tout en écrivant sur un autre disque. Cela revient à implémenter un verrouillage.

Fin de la journée

À la fin de la journée, le système aura de nombreux jetons fréquents avec une fréquence stockée en mémoire, et de nombreux autres jetons moins fréquents stockés dans plusieurs fichiers disque (et chaque fichier est trié).

Le système vide la carte en mémoire dans un fichier disque (trie-le). Maintenant, le problème devient la fusion d'un ensemble de fichiers de disque triés. En utilisant un processus similaire, nous obtiendrions un fichier disque trié à la fin.

Ensuite, la dernière tâche consiste à fusionner le fichier disque trié dans la base de données d'archives. Dépend de la taille de la base de données d'archive, l'algorithme fonctionne comme ci-dessous s'il est assez grand:

   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 qu'après un certain temps, le nombre d'insertions deviendra de plus en plus petit. De plus en plus d'opérations se feront uniquement sur la mise à jour. Et cette mise à jour ne sera pas pénalisée par l'index.

J'espère que toute cette explication aiderait. :)


Je ne comprends pas. Quel genre de tri ou de comparaisons significatives pourrait-on faire dans les ID entiers des mots? Les chiffres ne sont-ils pas arbitraires?
Dimitris Andreou

En outre, compter les fréquences de mots est le tout premier exemple du document MapReduce de Google ( labs.google.com/papers/mapreduce.html ), le résolvant de manière évolutive en une poignée de lignes. Vous pouvez même déplacer vos données vers google app angine et faire un tel MapReduce ( code.google.com/p/appengine-mapreduce )
Dimitris Andreou

@Dimitris Andreou: Le tri sur les entiers serait plus rapide sur les chaînes. En effet, la comparaison de deux entiers est plus rapide que la comparaison de deux chaînes.
SiLent SoNG

@Dimitris Andreou: la mapreduce de Google est une belle approche distribuée pour résoudre ce problème. Ah! Merci d'avoir fourni les liens. Oui, ce serait bien pour nous de trier en utilisant plusieurs machines. Belle approche.
SiLent SoNG

@Dimitris Andreou: Jusqu'à présent, je n'ai envisagé qu'une approche de tri à une seule machine. Quelle belle idée de trier dans la distribution.
SiLent SoNG

4

Vous pouvez utiliser une table de hachage combinée à un arbre de recherche binaire . Implémentez un <search term, count>dictionnaire qui vous indique combien de fois chaque terme de recherche a été recherché.

Évidemment, itérer toute la table de hachage toutes les heures pour obtenir le top 10 est très mauvais. Mais c'est Google dont nous parlons, vous pouvez donc supposer que les dix premiers obtiendront tous, disons plus de 10 000 visites (c'est probablement un nombre beaucoup plus important cependant). Ainsi, chaque fois que le nombre d'un terme de recherche dépasse 10 000, insérez-le dans le BST. Ensuite, toutes les heures, vous n'avez qu'à obtenir les 10 premiers du BST, qui devraient contenir relativement peu d'entrées.

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


La partie la plus délicate consiste à faire en sorte qu’un terme prenne la place d’un autre dans le rapport mensuel (par exemple, le «dépassement de pile» peut avoir 50 000 appels au cours des deux derniers mois, mais seulement 10 000 le mois dernier, tandis que «amazon» peut en avoir 40 000 pour les deux derniers mois mais 30 000 pour le mois dernier. Vous voulez que "amazon" vienne avant le "débordement de pile" dans votre rapport mensuel). Pour ce faire, je stockerais, pour tous les principaux termes de recherche (au-dessus de 10 000 recherches de tous les temps), une liste de 30 jours qui vous indique combien de fois ce terme a été recherché chaque jour. La liste fonctionnerait comme une file d'attente FIFO: vous supprimez le premier jour et en insérez un nouveau chaque jour (ou chaque heure, mais vous devrez peut-être stocker plus d'informations, ce qui signifie plus de mémoire / d'espace. Si la mémoire n'est pas un problème, faites it, sinon optez pour cette "approximation"

Cela semble être un bon début. Vous pouvez alors vous soucier d'élaguer les termes qui ont plus de 10 000 hits mais qui n'en ont pas eu beaucoup depuis longtemps et des trucs comme ça.


3

cas i)

Conservez une table de hachage pour tous les termes de recherche, ainsi qu'une liste triée des dix premiers, distincte de la table de hachage. Chaque fois qu'une recherche se produit, incrémentez l'élément approprié dans la table de hachage et vérifiez si cet élément doit maintenant être remplacé par le 10ème élément de la liste des dix premiers.

O (1) recherche de la liste des dix premiers, et insertion max O (log (n)) dans la table de hachage (en supposant des collisions gérées par un arbre binaire auto-équilibré).

cas ii) Au lieu de maintenir une é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 s'il doit basculer avec le terme après lui. Un arbre binaire auto-équilibré pourrait bien fonctionner pour cela, car nous devons également pouvoir l'interroger rapidement (plus d'informations à ce sujet plus tard).

De plus, nous maintenons également une liste des «heures» sous la forme d'une liste FIFO (file d'attente). Chaque élément «heure» contiendrait une liste de toutes les recherches effectuées au cours de cette heure particulière. Ainsi, par exemple, notre liste d'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, toutes les heures: si la liste a au moins 720 heures (c'est le nombre d'heures dans 30 jours), regardez le premier élément de la liste, et pour chaque terme de recherche, décrémentez cet élément dans la table de hachage du montant approprié . Ensuite, supprimez cet élément de la première heure de la liste.

Disons que nous sommes à l'heure 721 et que nous sommes prêts à regarder la première heure de notre liste (ci-dessus). Nous décrémentions les éléments gratuits de 56 dans la table de hachage, les images amusantes de 321, etc., puis supprimions complètement l'heure 0 de la liste car nous n'aurons plus jamais besoin de la regarder à nouveau.

La raison pour laquelle nous maintenons une liste triée de tous les termes qui permet des requêtes rapides est que toutes les heures après que nous parcourons les termes de recherche d'il y a 720 heures, nous devons nous assurer que la liste des dix premiers reste triée. Ainsi, lorsque nous décrémentons les `` éléments gratuits '' de 56 dans la table de hachage par exemple, nous vérifierions où ils appartiennent maintenant dans la liste. Parce que c'est un arbre binaire auto-équilibré, tout cela peut être accompli en un temps O (log (n)).


Edit: Sacrifier la précision pour l'espace ...

Il peut être utile d'implémenter également une grande liste dans le premier, comme dans le second. Ensuite, nous pourrions appliquer l'optimisation d'espace suivante dans les deux cas: Exécutez une tâche cron pour supprimer tous les éléments sauf les x premiers de la liste. Cela réduirait l'espace requis (et, par conséquent, rendrait les requêtes sur la liste plus rapides). Bien sûr, cela aboutirait à un résultat approximatif, mais cela est autorisé. x peut être calculé avant de déployer l'application en fonction de la mémoire disponible, et ajusté dynamiquement si davantage de mémoire devient disponible.


2

Pensée approximative ...

Pour le top 10 de tous les temps

  • Utilisation d'une collection de hachage où un décompte pour chaque terme est stocké (nettoyer les termes, etc.)
  • Un tableau trié qui contient les 10 premiers en cours, un terme / nombre ajouté à ce tableau chaque fois que le nombre d'un terme devient égal ou supérieur au plus petit nombre du tableau

Pour le top 10 mensuel mis à jour toutes les heures:

  • Utilisation d'un tableau indexé sur le nombre d'heures écoulées depuis le démarrage du modulo 744 (le nombre d'heures pendant un mois), dont les entrées de tableau consistent en une collection de hachage où un décompte pour chaque terme rencontré pendant cette tranche horaire est stocké. Une entrée est réinitialisée chaque fois que le compteur horaire change
  • les statistiques du tableau indexé sur le créneau horaire doivent être collectées chaque fois que le compteur du créneau horaire actuel change (une fois par heure au plus), en copiant et aplatissant le contenu de ce tableau indexé sur créneaux horaires

Euh ... ça a du sens? Je n'ai pas pensé à ça comme je le ferais dans la vraie vie

Ah oui, j'ai oublié de mentionner, le "copier / aplatir" horaire requis pour les statistiques mensuelles peut en fait réutiliser le même code utilisé pour le top 10 de tous les temps, un bel effet secondaire.


2

Solution exacte

Tout d'abord, une solution qui garantit des résultats corrects, mais qui nécessite beaucoup de mémoire (une grande carte).

Variante "de tous les temps"

Maintenez une carte de hachage avec des requêtes comme clés et leurs décomptes comme valeurs. De plus, gardez une liste des 10 requêtes les plus fréquentes à ce jour et le décompte du 10e dénombrement le plus fréquent (un seuil).

Mettez constamment à jour la carte lorsque le flux de requêtes est lu. Chaque fois qu'un nombre dépasse le seuil actuel, procédez comme suit: supprimez la 10e requête de la liste "Top 10", remplacez-la par la requête que vous venez de mettre à jour et mettez également à jour le seuil.

Variante "Mois dernier"

Gardez la même liste "Top 10" et mettez-la à jour de la même manière que ci-dessus. Aussi, gardez une carte similaire, mais cette fois, stockez des vecteurs de 30 * 24 = 720 comptage (un pour chaque heure) comme valeurs. Toutes les heures, procédez comme suit pour chaque clé: supprimez le compteur le plus ancien du vecteur, ajoutez-en un nouveau (initialisé à 0) à la fin. Supprimez la clé de la carte si le vecteur est entièrement nul. De plus, toutes les heures, vous devez calculer la liste des «10 meilleurs» à partir de zéro.

Remarque: Oui, cette fois, nous stockons 720 entiers au lieu d'un, mais il y a beaucoup moins de clés (la variante de tous les temps a une très longue queue).

Approximations

Ces approximations ne garantissent pas la solution correcte, mais consomment moins de mémoire.

  1. Traitez chaque N-ième requête en ignorant le reste
  2. (Pour la variante de tous les temps uniquement) Conservez au plus M paires clé-valeur dans la carte (M doit être aussi grand que vous pouvez vous le permettre). C'est une sorte de cache LRU: chaque fois que vous lisez une requête qui n'est pas dans la carte, supprimez la requête la moins récemment utilisée avec le compte 1 et remplacez-la par la requête actuellement traitée.

J'aime l'approche probabiliste en approximation 1. Mais en utilisant l'approximation 2 (cache LRU), que se passe-t-il si des termes qui n'étaient pas très populaires au départ le deviennent plus tard? Ne seraient-ils pas supprimés à chaque fois qu'ils sont ajoutés, car leur nombre serait très faible?
du

@del Vous avez raison, la seconde approximation ne fonctionnera que pour certains flux de requêtes. C'est moins fiable, mais en même temps nécessite moins de ressources. Remarque: vous pouvez également combiner les deux approximations.
Bolo

2

Top 10 des termes de recherche du mois dernier

L 'utilisation d' une indexation / structure de données efficace en mémoire, telle que des essais compacts (à partir d 'entrées wikipedia sur les essais ) définit approximativement une relation entre les besoins en mémoire et n - nombre de termes.

Dans le cas où la mémoire requise est disponible ( hypothèse 1 ), vous pouvez conserver des statistiques mensuelles exactes et les agréger chaque mois dans toutes les statistiques de temps.

Il y a aussi ici une hypothèse qui interprète le «mois dernier» comme une fenêtre fixe. Mais même si la fenêtre mensuelle glisse, la procédure ci-dessus montre le principe (le glissement peut être approché avec des fenêtres fixes de taille donnée).

Cela me rappelle la base de données à tour de rôle, à l'exception du fait que certaines statistiques sont calculées sur `` tous les temps '' (dans un sens où toutes les données ne sont pas conservées; rrd consolide les périodes sans tenir compte des détails en faisant la moyenne, en additionnant ou en choisissant des valeurs max / min, dans une tâche donnée, les détails perdus sont des informations sur les éléments à basse fréquence, qui peuvent introduire des erreurs).

Hypothèse 1

Si nous ne pouvons pas détenir des statistiques parfaites pour tout le mois, alors nous devrions être en mesure de trouver une certaine période P pour laquelle nous devrions être en mesure de tenir des statistiques parfaites. Par exemple, en supposant que nous ayons des statistiques parfaites sur une période P, qui va dans le mois n fois.
Les statistiques parfaites définissent la fonction f(search_term) -> search_term_occurance.

Si nous pouvons garder toutes nles tables de statistiques parfaites en mémoire, les statistiques mensuelles glissantes peuvent être calculées comme ceci:

  • ajouter des statistiques pour la période la plus récente
  • supprimer les statistiques de la période la plus ancienne (nous devons donc conserver ndes tableaux de statistiques parfaits)

Cependant, si nous ne conservons que le top 10 au niveau agrégé (mensuel), nous pourrons supprimer beaucoup de données des statistiques complètes de la période fixe. Cela donne déjà une procédure de travail qui a des besoins de mémoire fixes (en supposant une limite supérieure sur une table statistique parfaite pour la période P).

Le problème avec la procédure ci-dessus est que si nous ne conservons les informations que sur les 10 principaux termes d'une fenêtre glissante (de la même manière pour tous les temps), les statistiques seront correctes pour les termes de recherche qui culminent dans une période, mais pourraient ne pas voir le statistiques pour les termes de recherche qui apparaissent constamment au fil du temps.

Cela peut être compensé en conservant des informations sur plus des 10 principaux termes, par exemple les 100 premiers termes, en espérant que les 10 premiers seront corrects.

Je pense qu'une analyse plus approfondie pourrait relier le nombre minimum d'occurrences requis pour qu'une entrée fasse partie des statistiques (ce qui est lié à l'erreur maximale).

(Pour décider quelles entrées devraient faire partie des statistiques, on pourrait également surveiller et suivre les tendances; par exemple, si une extrapolation linéaire des occurrences dans chaque période P pour chaque terme vous indique que le terme deviendra significatif dans un mois ou deux, vous peut déjà commencer à le suivre. Un principe similaire s'applique pour supprimer le terme de recherche du pool suivi.)

Le pire des cas pour ce qui précède est lorsque vous avez beaucoup de termes presque aussi fréquents et qu'ils changent tout le temps (par exemple, si le suivi de seulement 100 termes, alors si les 150 premiers termes sont également fréquents, mais les 50 premiers sont plus souvent au cours du premier mois et de peur que souvent, quelque temps plus tard, les statistiques ne soient pas conservées correctement).

Il pourrait également y avoir une autre approche qui n'est pas fixée dans la taille de la mémoire (enfin à proprement parler, ce qui précède ne l'est pas non plus), qui définirait une signification minimale en termes d'occurrences / période (jour, mois, année, tous les temps) pour laquelle conserver le Statistiques. Cela pourrait garantir une erreur maximale dans chacune des statistiques lors de l'agrégation (voir à nouveau le tournoi à la ronde).


2

Qu'en est-il d'une adaptation de «l'algorithme de remplacement de page d'horloge» (également appelé «seconde chance»)? Je peux imaginer que cela fonctionne très bien si les demandes de recherche sont réparties uniformément (cela signifie que la plupart des termes recherchés apparaissent régulièrement plutôt que 5 millions de fois de suite et plus jamais).

Voici une représentation visuelle de l'algorithme: algorithme de remplacement de page d'horloge


0

Stockez le nombre de termes de recherche dans une table de hachage géante, où chaque nouvelle recherche entraîne l'incrémentation d'un élément particulier. Gardez une trace des 20 principaux termes de recherche; lorsque l'élément à la 11e place est incrémenté, vérifiez s'il a besoin d'échanger des positions avec # 10 * (il n'est pas nécessaire de garder le top 10 trié; tout ce qui vous importe est de faire la distinction entre le 10e et le 11e).

* Des vérifications similaires doivent être effectuées pour voir si un nouveau terme de recherche est à la 11ème place, donc cet algorithme se retrouve également dans d'autres termes de recherche - donc je simplifie un peu.


Vous voudrez limiter la taille de votre table de hachage. Et si vous obtenez un flux de recherches uniques? Vous devez vous assurer de ne pas vous empêcher de remarquer un terme recherché régulièrement mais rarement. Au fil du temps, cela pourrait être le terme de recherche le plus élevé, surtout si tous les autres termes de recherche sont des "événements actuels", c'est-à-dire beaucoup recherchés maintenant, mais pas tellement la semaine prochaine. En fait, des considérations comme celles-ci pourraient être des approximations que vous voudriez faire. Justifiez-les en disant que nous n'attraperons pas ce genre de choses car cela rend l'algorithme beaucoup plus coûteux en temps / espace.
cape1232

Je suis presque sûr que Google a un décompte de tout - certains décomptes ne sont cependant pas maintenus statiquement, mais plutôt calculés au besoin.
Ether

0

parfois la meilleure réponse est "je ne sais pas".

Je vais prendre un coup plus profond. Mon premier instinct serait d'alimenter les résultats dans un Q. Un processus traiterait continuellement les éléments entrant dans le Q. Le processus maintiendrait une carte de

terme -> compter

chaque fois qu'un élément Q est traité, vous recherchez simplement le terme de recherche et incrémentez le nombre.

En même temps, je maintiendrais une liste de références aux 10 premières entrées de la carte.

Pour l'entrée qui a été actuellement implémentée, voyez si son nombre est supérieur au nombre du nombre de la plus petite entrée dans les 10 premiers (si ce n'est déjà dans la liste). Si c'est le cas, remplacez le plus petit par l'entrée.

Je pense que cela fonctionnerait. Aucune opération ne demande beaucoup de temps. Vous devrez trouver un moyen de gérer la taille de la carte de comptage. mais cela devrait suffire pour une réponse d'entrevue.

Ils n'attendent pas de solution, ils veulent voir si vous pouvez réfléchir. Vous n'avez pas à écrire la solution sur-le-champ ...


12
La structure de données s'appelle a queue, Qest une lettre :).
IVlad

3
Si je menais l'entrevue, «je ne sais pas <stop>» ne serait certainement pas la meilleure réponse. Sois résonnable. Si vous ne savez pas, découvrez-le - ou du moins essayez.
Stephen

dans les interviews, quand je vois une personne avec hibernation sur sa 7 page reprendre 5 fois, et qu'elle ne peut pas me dire ce qu'est l'ORM, je termine l'entretien immédiatement. Je préfère qu'ils ne le mettent pas sur leur CV et disent simplement: "Je ne sais pas". Personne ne sait tout. @IVIad, je faisais semblant d'être un développeur C et j'essayais de sauver des bits ...;)
hvgotcodes

0

Une façon est que pour chaque recherche, vous stockez ce terme de recherche et son horodatage. De cette façon, trouver les dix premiers pour n'importe quelle période de temps est simplement une question de comparaison de tous les termes de recherche dans la période donnée.

L'algorithme est simple, mais l'inconvénient serait une plus grande consommation de mémoire et de temps.


0

Qu'en est-il de l'utilisation d'un arbre Splay avec 10 nœuds? Chaque fois que vous essayez d'accéder à une valeur (terme de recherche) qui n'est pas contenue dans l'arborescence, supprimez une feuille, insérez la valeur à la place et accédez-y.

L'idée derrière cela est la même que dans mon autre réponse . En supposant que les termes de recherche sont accédés uniformément / régulièrement, cette solution devrait très bien fonctionner.

Éditer

On pourrait aussi stocker d'autres termes de recherche dans l'arborescence (il en va de même pour la solution que je propose dans mon autre réponse) afin de ne pas supprimer un nœud auquel on pourrait accéder très prochainement. Plus on y stocke de valeurs, meilleurs sont les résultats.


0

Je ne sais pas si je comprends bien ou non. Ma solution utilise le tas. En raison des 10 principaux éléments de recherche, je crée un tas de taille 10. Ensuite, mettez à jour ce tas avec une nouvelle recherche. Si la fréquence d'une nouvelle recherche est supérieure au haut du tas (Max Heap), mettez-la à jour. Abandonnez celui avec la plus petite fréquence.

Mais, comment calculer la fréquence de la recherche spécifique sera compté sur autre chose. Peut-être que comme tout le monde l'a dit, l'algorithme de flux de données ...


0

Utilisez cm-sketch pour stocker le nombre de toutes les recherches depuis le début, gardez un tas min de taille 10 avec lui pour le top 10. Pour un résultat mensuel, gardez 30 cm-sketch / hash-table et min-heap avec, chacun commence comptage et mise à jour des derniers 30, 29 .., 1 jour. En tant que pass journalier, effacez le dernier et utilisez-le comme jour 1. Idem pour le résultat horaire, gardez 60 hash-table et min-heap et commencez à compter pour les 60, 59, ... dernières minutes. En une minute, effacez le dernier et utilisez-le comme minute 1.

Le résultat mensuel est précis dans une plage de 1 jour, le résultat horaire est précis dans une plage de 1 min


0

Le problème n'est pas résolu universellement lorsque vous avez une quantité fixe de mémoire et un flux «infini» (pensez très très grand) de jetons.

Une explication approximative ...

Pour voir pourquoi, considérez un flux de jetons qui a un jeton particulier (c'est-à-dire un mot) T tous les N jetons dans le flux d'entrée.

Supposons également que la mémoire puisse contenir des références (identifiant et nombre de mots) à au plus M jetons.

Avec ces conditions, il est possible de construire un flux d'entrée où le jeton T ne sera jamais détecté si le N est suffisamment grand pour que le flux contienne des M jetons différents entre les T.

Ceci est indépendant des détails de l'algorithme top-N. Cela ne dépend que de la limite M.

Pour voir pourquoi cela est vrai, considérez le flux entrant composé de groupes de deux jetons identiques:

T a1 a2 a3 ... a-M T b1 b2 b3 ... b-M ...

où les a et b sont tous des jetons valides différents de T.

Notez que dans ce flux, le T apparaît deux fois pour chaque ai et bi. Pourtant, il apparaît rarement assez rarement pour être vidé du système.

En commençant par une mémoire vide, le premier jeton (T) occupera un emplacement dans la mémoire (délimité par M). Ensuite, a1 consommera un emplacement, jusqu'à a- (M-1) lorsque le M est épuisé.

Quand aM arrive, l'algorithme doit abandonner un symbole alors que ce soit le T.Le symbole suivant sera b-1, ce qui provoquera le vidage de a-1, etc.

Ainsi, le T ne restera pas résidant en mémoire assez longtemps pour constituer un décompte réel. En bref, tout algorithme manquera un jeton de fréquence locale suffisamment basse mais de fréquence globale élevée (sur la longueur du flux).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.