J'ai un fichier CSV que je souhaite supprimer des lignes en double, mais il est trop grand pour s'adapter à la mémoire. J'ai trouvé un moyen de le faire, mais je suppose que ce n'est pas le meilleur moyen.
Chaque ligne contient 15 champs et plusieurs centaines de caractères, et tous les champs sont nécessaires pour déterminer l'unicité. Au lieu de comparer la ligne entière pour trouver un duplicata, je comparais une solution je pensais à accélérer cela augmente en trouvant un meilleur filtre pour réduire le nombre de passes nécessaires. En supposant que la longueur des lignes est distribuée uniformément, peut-être au lieu de p> et p> nous avons p> et p> où "n" aussi petit que la mémoire permettra. Mais cela utilise toujours la même méthode. P> Wayne Werner fourni une bonne solution pratique ci-dessous; J'étais curieux s'il y avait une meilleure façon / plus rapide / plus simple de le faire dans une perspective d'algorithme. P> P.s. Je suis limité à Python 2.5. P> P> hachage (ligne-as-chaîne) code> dans une tentative de sauvegarde de la mémoire. J'ai défini un filtre qui partitionne les données dans un nombre approximativement égal de lignes (par exemple des jours de la semaine) et chaque partition est suffisamment petite pour que la table de recherche des valeurs de hachage de cette partition correspondra à la mémoire. Je passe à travers le fichier une fois pour chaque partition, vérifiant les lignes uniques et les écrivez à un deuxième fichier (pseudo code): p>
6 Réponses :
Si vous voulez un moyen très simple de le faire, créez simplement une base de données SQLite: alors vous n'auriez pas à vous soucier de la logique de comparaison vous-même - juste laissez SQLite Prenez soin de vous pour vous. Cela ne sera probablement pas beaucoup plus rapide que de hacher les cordes, mais c'est probablement beaucoup plus facile. Bien sûr, vous modifiez le type stocké dans la base de données si vous le souhaitez, ou non le cas échéant. Bien sûr, puisque vous convertissez déjà les données en une chaîne, vous pouvez simplement avoir un seul champ. Beaucoup d'options ici. P> p>
Merci ww. C'est une bonne réponse pratique que je vais suivre lorsque ma réputation devient suffisamment élevée. J'étais plus curieux de la solution théorique ... "Oh, utilisez cet algorithme avec ces structures de données!" Je modifierai le poste pour le refléter.
+1 Si cela ne va pas entrer dans la mémoire, cela ne va pas s'adapter à la mémoire :) Vous devrez donc stocker vos résultats sur disque! SQLite indiquera vos données afin que ce soit FassStttTT.
Pensez à utiliser les paramètres SQLITE ... cur.execute ("Insert dans les valeurs XXX (?,?, ?,?,?,?,?)", (1,2,3,4,5)) code>
(1) En vous engageant après chaque rangée, tandis que nécessaire pour le faire vérifier la contrainte PK, va le rendre plutôt lent, n'est-ce pas? (2) Ne serait-il pas plus facile d'avoir une seule colonne dans la base de données?
@John, je ne suis pas sûr à quel point c'est rapide, je n'ai pas fait de comparaisons de vitesse, je ne peux donc pas dire. Si vous êtes intéressé, vous pouvez toujours écrire vos propres tests, poster une question, puis répondre à ce que les gens puissent bénéficier;) comme pour une colonne - j'ai mentionné que, étant donné que, puisque l'OP convertissait déjà les données en chaîne ...
@Joe, j'ai pensé à cela, puis j'ai compris que c'est plus une solution unique. Mais maintenant que vous en parlez, il est possible que les données ne soient pas correctement désinfectées simplement par accident. Sans parler de quelqu'un qui trébuche de cette solution sans expérience préalable SQL, ils peuvent l'utiliser dans un but sûr. J'ai changé ma réponse.
C'est plus facile et plus rapide; pas seulement plus sûr. Le moteur SQLite mettra en cache le texte de la requête déjà analysé et simplement ré-exécuter le plan avec les nouveaux paramètres.
@Joe, bon à savoir! Je n'ai pas eu plus qu'un besoin accessoire d'utiliser SQLite, alors mes connaissances sont assez fondamentales.
Vous faites fondamentalement une tresse de fusion et vous enlevez des entrées dupliquées.
briser l'entrée dans des morceaux de taille mémoire, triant chacune des pièces, puis fusionner les morceaux lors de la suppression des doublons est une idée sonore en général. p>
En fait, jusqu'à quelques concerts, je laisserais le système de mémoire virtuelle le gérer et écrire simplement: p>
Premièrement, il y a la faible probabilité que deux lignes réellement différentes puissent produire la même valeur de hachage. Deuxièmement, vous rendez la probabilité plus haute avec votre Caper "Réduire / Lambda": P> hachage (a) == hachage (b) code> ne signifie pas toujours que
a == b code> p>
>>> reduce(lambda x,y: x+y, ['foo', '1', '23'])
'foo123'
>>> reduce(lambda x,y: x+y, ['foo', '12', '3'])
'foo123'
>>>
Ah, merci de m'attaquer à cette collision "Caper". Un bon point. Un test d'adhésion est-il plus rapide dans un ensemble que dans une dict?
Autant que je sache, il n'y a aucune raison de s'attendre à une différence importante de la rapidité des tests d'adhésion. Le coût de la création d'une valeur de hachage utilisant le code Python au lieu du code C est probablement intéressant d'être étudié si vous envisagez de persister avec votre méthode d'origine.
Votre solution d'origine est légèrement incorrecte: vous pouvez avoir des lignes différentes de la même valeur (une collision de hachage) et votre code laisserait l'un d'eux. P>
En termes de complexité algorithmique, si vous attendez relativement peu de duplicats, je pense que la solution la plus rapide serait de numériser la ligne de fichier par ligne, en ajoutant le hachage de chaque ligne (comme vous l'avez fait), mais aussi de stocker l'emplacement. de cette ligne. Ensuite, lorsque vous rencontrez un hachage en double, cherchez à l'endroit d'origine pour vous assurer qu'il s'agit d'une copie de hachage et non seulement une collision de hachage, et si oui, recherchez et sautez la ligne. P>
Au fait, si les valeurs de CSV sont normalisées (c.-à-d. Les enregistrements sont considérés comme étant égaux si les lignes CSV correspondantes sont équivalentes d'octet-octet) équivalentes, vous n'avez pas besoin d'impliquer l'analyse de la CSV ici, faites simplement traiter des lignes de texte braliment. . p>
Selon le hachage, par exemple SHA1 aurait besoin de 2 ^ 80 (ou plus) de chèques pour trouver un faux match, qui, pour toute attaque non spécifiquement fabriquée, serait tout à fait sûr ....
Depuis que je suppose que vous devriez le faire de manière un peu régulière (sinon vous auriez piraté un script unique), et vous avez mentionné que vous avez été intéressé par une solution théorique, voici une possibilité.
Lisez les lignes d'entrée dans les arbres B, commandées par la valeur de hachage de chaque ligne d'entrée, écrivez-les sur le disque lorsque la mémoire remplit. Nous prenons soin de stocker, sur les arbres B, les lignes d'origine attachées au hasch (en tant que jeu, car nous ne nous soucions que de lignes uniques). Lorsque nous lisons un élément dupliqué, nous vérifions les lignes définies sur l'élément stocké et l'ajoutons si c'est une nouvelle ligne qui arrive à hacher à la même valeur. P>
Pourquoi les arbres B? Ils nécessitent moins de lectures de disque lorsque vous ne pouvez (ou voulez-vous que) de lire des parties de leur mémoire. Le degré (nombre d'enfants) sur chaque nœud dépend de la mémoire et du nombre de lignes disponibles, mais vous ne voulez pas avoir trop de nœuds. P>
Une fois que nous avons ces arbres B sur disque, nous comparez l'élément le plus bas de chacun d'eux. Nous enlevons le plus bas de tous, de tous les arbres B qui l'avez. Nous fusionnerons leurs lignes de lignes, ce qui signifie que nous n'avons laissé aucun doublé à gauche pour ces lignes (et aussi que nous n'avons plus de lignes que hachage à cette valeur). Nous écrivons ensuite les lignes de cette fusion dans la structure de sortie CSV. P>
Nous pouvons séparer la moitié de la mémoire pour la lecture des arbres B et la moitié pour conserver la sortie CSV en mémoire pendant un certain temps. Nous rinçons le CSV sur le disque lorsque sa moitié est pleine, ajoute à tout ce qui a déjà été écrit. La quantité de chaque arbre B que nous lisons à chaque étape peut être grossièrement calculé par (disponible_memory / 2) / number_of_btrees, arrondi, donc nous lisons des nœuds complets. P>
en pseudo-python: P>
ins = DictReader(...) i = 0 while ins.still_has_lines_to_be_read(): tree = BTree(i) while fits_into_memory: line = ins.readline() tree.add(line, key=hash) tree.write_to_disc() i += 1 n_btrees = i # At this point, we have several (n_btres) B-Trees on disk while n_btrees: n_bytes = (available_memory / 2) / n_btrees btrees = [read_btree_from_disk(i, n_bytes) for i in enumerate(range(n_btrees))] lowest_candidates = [get_lowest(b) for b in btrees] lowest = min(lowest_candidates) lines = set() for i in range(number_of_btrees): tree = btrees[i] if lowest == lowest_candidates[i]: node = tree.pop_lowest() lines.update(node.lines) if tree.is_empty(): n_btrees -= 1 if output_memory_is_full or n_btrees == 0: outs.append_on_disk(lines)
Que diriez-vous d'utiliser le module de trempe pour lire des morceaux de fichier jusqu'à la limite de mémoire et écrivez-les des morceaux de tri (ThePQ garde les choses toujours dans l'ordre tri). P>
ou vous pouvez attraper le premier mot en ligne et diviser le fichier en morceaux par cela. Ensuite, vous pouvez lire les lignes (peut-être faire ».join (ligne.split ()) pour unifier l'espacement / les onglets en ligne s'il est correct pour modifier l'espacement) dans Définir l'ordre alphabétique Effacement de l'ensemble entre les pièces (Ensemble Supprimage DUPLICATES) Pour obtenir des choses à moitié triées (SET n'est pas dans l'ordre, si vous voulez que vous puissiez lire dans le tas et écrire pour obtenir l'ordre trié, le dernier occurrence dans le réglage de l'ensemble des valeurs anciennes.) Vous pouvez également modifier la pièce.) et supprimez des lignes en double avec la solution de groupeBy de Joe Koberg. Enfin, vous pouvez rejoindre des morceaux ensemble (vous pouvez bien sûr faire l'écriture lorsque vous allez à la pièce par pièce au fichier final lors du tri des pièces) P>
Les lignes de sortie doivent-elles être dans le même ordre que sur le fichier d'entrée? Vous attendez-vous à de nombreuses répétitions ou si la taille du fichier de sortie devrait-elle garder plus ou moins le même ordre de grandeur que celui du fichier d'entrée (ou qui n'est pas prévisible)?
L'ordre des lignes dans le fichier de sortie n'est pas important. Pour ce cas particulier, il y a relativement peu de doublons. Pensez-vous que le nombre de doublons a porté dans l'affaire général?
Cela pourrait si, par exemple, les lignes uniques pourraient s'intégrer dans la mémoire (même si le fichier complet, avec le dupliqué, ne serait pas). Je dois partir pendant un moment, mais je vais faire une suggestion plus tard.
@JOHNC: Votre édition implique que vous êtes intéressé par des réponses qui ne sont pas bonnes ou non pratiques ou non une solution - pourquoi?
@John Machin: Je voulais transmettre que j'étais intéressé par une partie de la théorie de la solution. J'ai accepté de toute façon la réponse de Wayne Werner puisqu'il résout le problème.