Trouver l'élément qui se produit le plus dans un très gros fichier


12

J'ai entendu cette question d'interview demander beaucoup et j'espérais avoir des opinions sur les bonnes réponses: vous avez un gros fichier de 10+ Go et vous voulez savoir quel élément se produit le plus, quelle est la bonne façon pour faire ça?

Itérer et garder une trace dans une carte n'est probablement pas une bonne idée car vous utilisez beaucoup de mémoire, et garder une trace lorsque les entrées arrivent n'est pas la meilleure option car lorsque cette question est posée, le fichier existe généralement déjà.

J'ai également pensé à fractionner le fichier pour qu'il soit itéré et traité par plusieurs threads, puis que ces résultats soient combinés, mais le problème de mémoire pour les cartes est toujours là.


2
Quels sont les éléments du dossier? S'agit-il de cordes? Si vous prenez des caractères pour des éléments, la carte n'aura pas de problème de mémoire. Si les éléments sont des mots, je pense que ce ne serait pas un problème. Si vous avez toutes les sous-chaînes possibles, vous pouvez avoir des problèmes ...
Nejc

1
Si la condition était "un élément qui apparaît plus de la moitié du total des éléments", alors il y avait une solution linéaire.
st0le

Je pense que les éléments sont généralement des chaînes. Mais je ne vois pas comment la carte n'est pas un problème. Dans le pire des cas où chaque élément est unique, n'avez-vous pas simplement doublé vos besoins en mémoire?
Pat

1
Si l'algorithme candidat majoritaire de Boyer-Moore est applicable, il s'exécute en temps linéaire et est en place.
Juho

Réponses:


6

Lorsque vous avez un fichier très volumineux et de nombreux éléments, mais l'élément le plus courant est très courant - se produit fraction du temps - vous pouvez le trouver en temps linéaire avec des mots d'espace (le constante dans la notation est très petite, essentiellement 2 si vous ne comptez pas le stockage pour des choses auxiliaires comme le hachage). De plus, cela fonctionne très bien avec le stockage externe, car le fichier est traité en séquence un élément à la fois, et l'algorithme ne "regarde en arrière" jamais. Une façon de le faire est via un algorithme classique de Misra et Gries, voir ces notes de cours . Le problème est maintenant connu comme le problème des frappeurs lourds (les éléments fréquents étant les frappeurs lourds).O ( k ) O ( )>1/kO(k)O()

L'hypothèse selon laquelle l'élément le plus fréquent apparaît fraction du temps pour un petit nombre peut sembler forte mais elle est en quelque sorte nécessaire! Autrement dit, si vous aurez un accès séquentiel à votre fichier (et si le fichier est énorme, l'accès aléatoire sera trop cher), tout algorithme qui trouve toujours l'élément le plus fréquent dans un nombre constant de passages utilisera un espace linéaire dans le nombre d'éléments . Donc, si vous ne supposez rien sur l'entrée, vous ne pouvez pas battre une table de hachage. L'hypothèse selon laquelle l'élément le plus fréquent est très fréquent est peut-être le moyen le plus naturel de contourner les résultats négatifs.k>1/kk

Voici un croquis pour , c'est-à-dire lorsqu'il y a un seul élément qui se produit plus de la moitié du temps. Ce cas particulier est connu sous le nom d'algorithme de vote majoritaire et est dû à Boyer et Moore. Nous garderons un seul élément et un seul décompte. Initialisez le nombre à 1 et stockez le premier élément du fichier. Ensuite, traitez le fichier en séquence:k=2

  • si l'élément actuel du fichier est le même que l'élément stocké, augmentez le nombre d'un
  • si l'élément actuel du fichier est différent de l'élément stocké, diminuez le nombre d'un
  • si le nombre mis à jour est de 0, "supprimer" l'élément stocké et stocker l'élément actuel du fichier; augmenter le nombre à 1
  • passer à l'élément suivant du fichier

Un peu de réflexion sur cette procédure vous convaincra que s'il existe un élément "majoritaire", c'est-à-dire un élément qui se produit plus de la moitié du temps, alors cet élément sera l'élément stocké après le traitement de tout le fichier.

Pour général , vous gardez éléments et compte, et vous initialisez les éléments au premier éléments distincts du fichier et les comptes au nombre de fois chacun de ces éléments apparaît avant de voir le -ème élément distinct. Ensuite, vous exécutez essentiellement la même procédure: le nombre d'un élément est augmenté chaque fois qu'il est rencontré, tous les nombres d'éléments sont diminués si un élément qui n'est pas stocké est rencontré, et lorsqu'un certain nombre est nul, cet élément est expulsé en faveur de la élément courant du fichier. Il s'agit de l'algorithme Misra-Gries.k - 1 k - 1 k kkk1k1kk

Vous pouvez bien sûr utiliser une table de hachage pour indexer les éléments stockés. À la fin, cet algorithme est garanti pour retourner tout élément qui se produit plus de fraction du temps. C'est essentiellement le mieux que vous puissiez faire avec un algorithme qui effectue un nombre constant de passages sur le fichier et ne stocke que des mots .1 / k O ( k )k11/kO(k)

Une dernière chose: après avoir trouvé candidats "frappeurs lourds" (c'est-à-dire des éléments fréquents), vous pouvez effectuer un autre passage sur le fichier pour compter la fréquence de chaque élément. De cette façon, vous pouvez classer les éléments entre eux et vérifier s'ils se produisent tous plus de fraction du temps (s'il y a moins de tels éléments, certains des éléments renvoyés par l'algorithme peuvent être des faux positifs ).kk - 11/kk1


Vous ne pouvez pas utiliser les algorithmes Boyer-Moore ou Misra-Gries-Demaine. Le problème tel qu'énoncé est différent: vous ne recherchez pas un élément majoritaire, mais un élément dont les occurrences sont> = des occurrences de tous les éléments. Voici un simple contre-exemple. Soit n le nombre total d'éléments, tel que n = 2k + 1 . Soit les k premiers éléments à 0, les k suivants à 1 et le dernier à 2. L'algorithme de Boyer-Moore rapportera le dernier élément, 2, comme candidat majoritaire potentiel. Mais, pour ce cas particulier, la sortie doit être 0 ou 1.
Massimo Cafaro

@MassimoCafaro je ne peux pas analyser l'expression "dont les occurrences ... les éléments". de toute façon, il est bien connu que trouver l'élément le plus fréquent dans passe la mémoire requise ! donc si vous voulez une petite empreinte mémoire, vous devez faire une hypothèse supplémentaire, l'hypothèse du frappeur lourd étant la plus naturelle pour moi. O(1)Ω(n)
Sasho Nikolov

Je viens de souligner que si vous faites une hypothèse erronée, vous pouvez obtenir de mauvais résultats. Quoi de mieux, une petite empreinte mémoire et un résultat potentiellement incorrect ou le résultat correct même si cela vous coûte plus de mémoire? Si je devais choisir un résultat potentiellement incorrect, j'opterais pour un algorithme aléatoire plutôt que pour Boyer-Moore en supposant que quelque chose que je ne sais pas est réellement vrai.
Massimo Cafaro,

@MassimoCafaro qui n'est pas un compromis que vous devez prendre. comme je l'ai souligné, un seul passage sur le fichier vérifie facilement si l'hypothèse est satisfaite!
Sasho Nikolov

@MassimoCafaro et ce n'est que la solution triviale! l'hypothèse peut être vérifiée avec une probabilité élevée avec un croquis CM sans passes supplémentaires.
Sasho Nikolov

3

La réponse évidente est bien sûr de conserver une carte de hachage et de stocker un compteur de l'occurrence des éléments lorsque vous vous déplacez dans le fichier comme Nejc l'a déjà suggéré. C'est (en termes de complexité temporelle) la solution optimale.

Si toutefois vos besoins en espace sont restreints, vous pouvez effectuer un tri externe sur le fichier, puis rechercher la plus longue série consécutive d'éléments égaux. Les éléments suivants doivent avoir une empreinte mémoire constante et peuvent être effectués dansΘ(nlogn).


Pourriez-vous nous en dire plus sur l'approche de codage Huffman? J'ai déjà écrit un encodeur Huffman mais cela fait un moment, comment l'utiliseriez-vous exactement dans ce cas?
Pat

@Pat Nevermind cette partie, il était trop tôt le matin et je pensais que cela aurait du sens de compresser l'entrée.
Jernej

1

Si l'élément le plus courant est plus courant que l'élément commun suivant par une marge substantielle et que le nombre d'éléments différents est petit par rapport à la taille du fichier, vous pouvez échantillonner au hasard quelques éléments et renvoyer l'élément le plus courant de votre échantillon.


De plus, s'il existe un petit nombre d'éléments se produisant plusieurs fois, vous pouvez les trouver par échantillonnage, puis compter uniquement ces éléments exactement.
Max
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.