Étant donné un fichier L avec un entier non négatif par ligne et un fichier texte F, quel serait un moyen rapide de conserver uniquement ces lignes dans F, dont le numéro de ligne apparaît dans le fichier L?
Exemple:
$ cat L.txt
1
3
$ cat F.txt
Hello World
Hallo Welt
Hola mundo
$ command-in-question -x L.txt F.txt
Hello World
Hola mundo
Je recherche une commande capable de gérer un fichier L avec 500 millions d'entrées ou plus; le fichier L est trié numériquement.
Remarque: je suis à mi-chemin d'une implémentation pour un, command-in-question
mais je me demandais simplement si on pouvait également utiliser certains outils Unix ici.
Mise à jour: Merci pour toutes les réponses, j'ai beaucoup appris aujourd'hui! J'aimerais accepter plus d'une réponse, mais ce n'est pas possible.
Réponses:
En
C
omettant les messages d'erreur significatifs:la source
xsel -bo | cc -xc - -o cselect
. Et cela a juste fonctionné - il n'a besoin que des deux bibliothèques.LINE_MAX
votre version, vous travaillez donc probablement avec de très grandes lignes dans vos fichiers. J'ai mis à jour le A avec une version utilisantgetline()
pour supprimer la limite de taille de ligne.LINE_MAX
,getline
semble donc juste.J'utiliserais
awk
, mais ne stockerais pas tout le contenu de laL.txt
mémoire et ferais des recherches de hachage inutiles ;-).la source
n
, sinon (en l' état ) , il manque1
àL.txt
command-in-question
script, alors vous ne pouvez pas avoir le nom de fichier incorporé dans le code.-v list="$opt_x"
ne fonctionne pas non plus à cause du traitement antislash effectué par awk dessus. C'est pourquoi j'utilise plutôt ENVIRON ici.grep -n | sort | sed | cut
Cela devrait fonctionner assez rapidement (certains tests chronométrés sont inclus ci-dessous) avec une entrée de n'importe quelle taille. Quelques notes sur comment:
export LC_ALL=C
./F
empilé en ligne avec./L
le fichier de son lineno, les seuls caractères dont nous devrons vraiment nous soucier sont les[0-9]
chiffres ASCII et les:
deux - points.grep -n ''
LINENO:
dans la tête de chaque ligne dans stdin - ou<./F
.sort -t: -nmk1,1 ./L -
sort
néglige de trier ses fichiers d'entrée, et au lieu de cela (correctement) présume qu'ils sont triés et les-m
efface dans l'-numerically
ordre trié, ignorant fondamentalement tout ce qui dépasse tout caractère de côlon-k1,1
se produisant de-t:
toute façon.sort
produira un seul flux où n'importe quel lineno./L
précédera immédiatement les lignes correspondantes./F
../L
Les lignes viennent toujours en premier car elles sont plus courtes.sed /:/d\;n
/:/
deux points,d
supprimez-la de la sortie. Sinon, imprimez automatiquement la ligne actuelle et lan
ligne ext.sed
pruneauxsort
sortie S » à seulement paires de lignes successives qui ne correspondent pas à deux points et la ligne suivante - ou, à seulement une ligne de./L
puis le lendemain.cut -sd: -f2-
cut
-s
supprime de la sortie celles de ses lignes d'entrée qui ne contiennent pas au moins une de ses-d:
chaînes d'élimitation - et donc./L
les lignes sont complètement élaguées.:
délimité par deux-f
points estcut
absent - et il en va de même pour tousgrep
les lineno insérés.petit test d'entrée
... génère 5 lignes d'entrée d'échantillon. Alors...
... impressions ...
de plus grands tests chronométrés
J'ai créé quelques fichiers assez volumineux:
... qui a mis 5mil de lignes dedans
/tmp/F
et 1,5mil de lignes sélectionnées aléatoirement dans/tmp/L
. J'ai ensuite fait:Il a imprimé:
(J'ai ajouté les barres obliques inverses là-bas)
Parmi les solutions actuellement proposées ici, celle-ci est la plus rapide de toutes, sauf une lorsqu'elle est opposée à l'ensemble de données généré ci-dessus sur ma machine. Parmi les autres, un seul a failli prétendre à la deuxième place, et c'est
perl
ici meuh .Ce n'est en aucun cas la solution originale proposée - elle a chuté d'un tiers de son temps d'exécution grâce aux conseils / inspiration offerts par d'autres. Voir l'historique des publications pour des solutions plus lentes (mais pourquoi?) .
En outre, il convient de noter que certaines autres réponses pourraient très bien lutter si ce n'était pour l'architecture multi-CPU de mon système et l'exécution simultanée de chacun des processus de ce pipeline. Ils travaillent tous en même temps - chacun sur son propre cœur de processeur - en contournant les données et en faisant leur petite partie de l'ensemble. C'est vraiment cool.
mais la solution la plus rapide est ...
Mais ce n'est pas la solution la plus rapide. La solution la plus rapide offerte ici, les mains vers le bas, est le programme C . Je l'ai appelé
cselect
. Après l'avoir copié dans mon presse-papiers X, je l'ai compilé comme:J'ai ensuite fait:
... et les résultats étaient ...
la source
sed -ne'/:/!{n;p;}' | cut -d: -f2-
au lieu desed -ne'/:/!N;/\n/s/[^:]*://p'
sed
s - ce quesed
j'utilise est l'héritagesed
- vous pouvez voir laalias
valeur dans lestime
résultats. Soit dit en passant, mon package hérité est compilé statiquement sur une libl musl - l'implémentation regex pour laquelle est basée sur TRE . Lorsque je le passe au GNUsed
- et que je l'exécute sanscut
- cela ajoute une seconde complète au temps de réalisation (2,8 secondes) - l'aggrave de plus d'un tiers. Et c'est seulement 0,3 seconde plus rapide que la vôtre sur mon système.sort -mn
par opposition àsort -nmk1,1
peut-être mieux car vous n'avez pas besoin de faire le fractionnement ici (non testé)-n
est spécifié juste pour faire la première chaîne numérique sur une ligne, donc je me suis dit, ok-mn
ou-nm
et, pour une raison quelconque, les seules fois où il est descendu en dessous de 2 secondes dans le temps de réalisation, c'est quand j'ai ajouté toutes les options telles quelles. C'est bizarre - et c'est la raison pour laquelle hier je n'ai pas abordé la question-m
en premier lieu - je savais de quoi je parlais, mais cela semblait fonctionner comme une sorte de chose d'auto-optimisation. Fait intéressant, l'héritagesort
a une-z
option de longueur de chaîne qui ne s'applique qu'aux-[cm]
....-n
n'est pas la première chaîne numérique de la ligne . Il considère simplement la ligne comme un nombre etabc 123
serait donc 0. Donc, cela ne peut pas être moins efficace qu'avec-t: -k1,1
J'utiliserais
awk
:Mise à jour: j'ai fait des mesures de performance; il semble que cette version évolue encore mieux avec des ensembles de données très volumineux (comme c'est le cas avec les exigences énoncées), car la comparaison est très rapide et compense l'effort nécessaire pour construire la table de hachage.
la source
awk
ne peuvent pas gérer de tels ensembles de données. - J'utilise GNUawk
et il n'y a aucun problème; le test avec 500 millions de lignes de données a nécessité 7 minutes.real 16m3.468s
-user 15m48.447s
-sys 0m10.725s
. Il a utilisé 3,3 Go de RAM pour tester une taille de 1 / 10eL
avec 50 000 000 lignes; etF
avec 500 000 000 lignes - vs temps pour awk anser de Stéphane Chazelas:real 2m11.637s
-user 2m2.748s
-sys 0m6.424s
- Je n'utilise pas de boîte rapide, mais la comparaison est intéressante.seq
sortie et un plus petit, sous - ensemble de même choisi au hasard dans L .Juste pour être complet: nous pouvons fusionner l'excellent script awk dans la réponse de Stéphane Chazelas, et le script perl dans la réponse de kos mais sans garder la liste entière en mémoire, dans l'espoir que perl soit plus rapide que awk. (J'ai changé l'ordre des arguments pour correspondre à la question d'origine).
la source
awk
. Il est à peu près aussi rapide que le mien - j'ai testé les deux trois fois tout à l'heure et chaque fois que le mien a géré mon test de ligne de 5mil en 1,8 ... secondes et le vôtre 1,9 ... secondes à chaque fois. Le code gen de testset est dans ma réponse si vous vous en souciez, mais le fait est qu'il est très bon. De plus, la sortie est correcte - je ne peux toujours pas faire leawk
travail ... Pourtant, nos deux réponses sont honteuses par FloHimself .awk
s. Sur votre échantillon, j'obtiens 1,4s avec gawk (4s pour Janis '), 0,9s avec mawk, 1,7s avec cette solution perl, 2,3s avec kos', 4,5s avec le vôtre (GNU sed) et 1,4s avec le vôtre ( GNU sed) et mon amélioration suggérée (et 0,5 s pour la solution C).J'ai écrit un simple script Perl pour cela:
Usage: script.pl inputfile_f inputfile_f
F.txt
L.txt
L.txt
dans un tableauF.txt
ligne par ligne, en suivant son numéro de ligne actuel et l'index du tableau actuel; augmente leF.txt
numéro de ligne actuel; si leF.txt
numéro de ligne actuel correspond au contenu du tableau à l'index du tableau actuel, il imprime la ligne actuelle et augmente l'indexConsidérations de coût et de complexité :
Considérant le coût pour effectuer les affectations, le coût pour faire les comparaisons et le coût pour imprimer les lignes, étant donné N 1 comme le nombre de lignes
F.txt
et N 2 comme le nombre de lignesL.txt
, lawhile
boucle s'exécute au plus N 1 fois, conduisant à 2N 1 + N 2 affectations (en supposant évidemment N 1 > N 2 ), à 2N 1 comparaisons et à N 2 impressions; étant donné que le coût de chaque opération est égal, le coût total pour exécuter lawhile
boucle est de 4N 1 + 2N 2 , ce qui conduit à une complexité du script de O (N).Testez sur un fichier d'entrée de 10 millions de lignes :
En utilisant un
F.txt
fichier de 10 millions de lignes contenant des lignes aléatoires de 50 caractères et unL.txt
fichier de 10 millions de lignes contenant des nombres de 1 à 10000000 (pire scénario):la source
Cette solution perl est plus rapide que les autres solutions awk ou perl de 20% environ, mais pas aussi vite que la solution en C.
la source
Puisque L.txt est trié, vous pouvez utiliser join. Il suffit de numéroter chaque ligne dans F.txt, de joindre les deux fichiers, puis de supprimer le numéro de ligne. Aucun gros fichier intermédiaire n'est nécessaire.
En fait, ce qui précède va modifier vos lignes de données en remplaçant tous les espaces blancs par un seul espace. Pour garder la ligne intacte, vous devez choisir comme délimiteur un caractère qui n'apparaît pas dans vos données, par exemple "|". Le cmd est alors
Le premier sed supprime les espaces de tête de la sortie "cat -n" et remplace l'onglet. Le deuxième sed supprime le numéro de ligne et "|".
la source
join L.txt <(nl F.txt )
mais cela ne fonctionnera pas sur les gros fichiers. Bienvenue sur le site, d'ailleurs, ce n'est pas souvent que nous obtenons des réponses aussi claires et bien formatées de nouveaux utilisateurs!join
/comm
ne peut pas fonctionner avec une entrée triée numériquement.join -t' ' <(<L.txt awk '{printf("%010s\n",$0)}') <(<F.txt awk '{printf("%010s %s\n",NR,$0)}') | cut -d' ' -f2-
- C'était lent! - et même lorsque j'ai alimenté des fichiers préparés avec des touches 0 remplies appropriéesjoin -t' ' L.txt F.txt | cut -d' ' -f2-
, c'était toujours lent (sans compter le temps de préparation) - plus lent que laawk
réponse de @Janis (où j'ai posté un commentaire sur les temps réels pris pour les deux sa et la réponse de @ StéphaneChazelasjoin
+ était vs Stéphane Chazelas en utilisant 50 millions de lignes, 500 millions de lignes.awk printf
real 20m11.663s user 19m35.093s sys 0m10.513s
real 2m11.637s user 2m2.748s sys 0m6.424s
L
F
Pour être complet, une autre tentative de
join
solution:Cela fonctionne en formatant la colonne de numéro de ligne qui joint fonctionne comme une longueur fixe avec des zéros non significatifs, de sorte que les nombres comportent toujours 15 chiffres. Cela contourne le problème de jointure n'aimant pas l'ordre de tri numérique normal, car la colonne a effectivement été forcée d'être un tri par dictionnaire.
nl
est utilisé pour ajouter des numéros de ligne dans ce format à F.txt. Malheureusement,sed
doit être utilisé pour reformater la numérotation dans L.txt.Cette approche semble fonctionner correctement sur les données de test générées à l'aide de la méthode de @ mikeserv. Mais c'est encore très lent - la solution c est 60 fois plus rapide sur ma machine. environ 2/3 du temps est passé
sed
et 1/3 pojoin
. Il y a peut-être une meilleure expression sed ...la source
nl
super cool, mais vous ne pouvez pas l'utiliser de manière robuste sur des entrées non testées. L'une des choses qui le rend si cool est son séparateur de pages logique-d
. Par défaut, s'il y a une ligne en entrée composée uniquement des cordes:\`
(mais sans le grave arrière) 1, 2, 3 ou trois fois de suite, vos décomptes deviendront un peu fous. Expérimentez avec - c'est assez soigné. En particulier, regardez ce qui se passe lorsque nl` lit une ligne avec 1 chaîne de délimiteur, puis un autre avec 3 ou 2Étant donné que la réponse acceptée est en C, je me suis dit que c'était OK de lancer une solution python ici:
Si vous utilisez une bibliothèque externe comme numpy, une solution serait encore plus élégante:
la source