Datei nach Zeilennummer filtern

17

Was wäre bei einer Datei L mit einer nicht-negativen Ganzzahl pro Zeile und Textdatei F ein schneller Weg, um nur die Zeilen in F zu belassen, deren Zeilennummer in Datei L erscheint?

Beispiel:

$ 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

Ich suche nach einem Befehl, der eine Datei L mit 500 Millionen oder mehr Einträgen verarbeiten kann. Datei L ist numerisch sortiert.

Hinweis: Ich bin auf halbem Weg durch eine Implementierung für eine, command-in-questionaber ich habe mich nur gefragt, ob man möglicherweise auch hier einige Unix-Tools verwenden kann.


Update: Vielen Dank für die Antworten, ich habe heute viel gelernt! Ich würde gerne noch eine Antwort annehmen, aber das ist nicht möglich.

Miku
quelle
Wie viele Zeilen enthält die Datei F?
Peter.O
1
Es gibt mindestens so viele Zeilen in F wie in L.
miku 13.06.15

Antworten:

8

Mit CWeglassen aussagekräftige Fehlermeldungen:

#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[]) {

    FILE *L;
    FILE *F;

    unsigned int to_print;
    unsigned int current = 0;
    char *line = NULL;
    size_t len = 0;

    if ((L = fopen(argv[1], "r")) == NULL) {
        return 1;
    } else if ((F = fopen(argv[2], "r")) == NULL) {
        fclose(L);
        return 1;
    } else {

        while (fscanf(L, "%u", &to_print) > 0) {
            while (getline(&line, &len, F) != -1 && ++current != to_print);
            if (current == to_print) {
                printf("%s", line);
            }
        }

        free(line);
        fclose(L);
        fclose(F);
        return 0;
    }
}
FloHimself
quelle
2
Dies ist die performanteste Antwort hier. Zumindest ist es so bei meinen Tests. Falls jemand interessiert ist, kompilierte ich es so: xsel -bo | cc -xc - -o cselect. Und es hat einfach funktioniert - es braucht nur die zwei Bibliotheken.
mikeserv
1
Danke, das ist großartig! Ich hoffe, es macht Ihnen nichts aus, aber ich habe Ihren Code in ein kleines Tool verpackt .
Miku
1
@miku Na los, ich bin froh, dass ich helfen konnte. Mir ist aufgefallen LINE_MAX, dass Sie Ihre Version erweitert haben, sodass Sie wahrscheinlich mit sehr großen Zeilen in Ihren Dateien arbeiten. Ich habe das A mit einer Version aktualisiert, mit der getline()die Zeilengrößenbeschränkung aufgehoben wird.
FloHimself
@FloHimself, na ja, danke nochmal:) In der Tat könnten einige Eingabezeilen übersteigen LINE_MAX, also getlinescheint es genau richtig.
Miku
10

Ich würde verwenden awk, aber nicht den gesamten Inhalt L.txtim Speicher speichern und unnötige Hash-Lookups durchführen ;-).

list=L.txt file=F.txt
LIST="$list" awk '
  function nextline() {
    if ((getline n < list) <=0) exit
  }
  BEGIN{
    list = ENVIRON["LIST"]
    nextline()
  }
  NR == n {
    print
    nextline()
  }' < "$file"
Stéphane Chazelas
quelle
Genau, ich habe Hash-Maps ausprobiert und sie würden den Speicher übersteigen. Bitsets verschaffen Ihnen mehr Headroom. Wenn Sie jedoch die Tatsache verwenden, dass die Eingabe sortiert ist, können Sie dieses (Raum-) Problem vollständig beseitigen.
Miku
1
@ Janis; ist das nicht nur ein Fall von guter Standard-Codierungspraxis:
Keine
1
@ StéphaneChazelas: Es muss vor der Schleife Initialisierung n, sonst (wie sie ist) es fehlt 1inL.txt
Peter.O
1
@ Peter.O, hoppla, das habe ich versucht mit NR> = n anzusprechen, aber das war falsch. Sollte jetzt besser sein.
Stéphane Chazelas
1
@ Janis, die Idee war, dass, wenn dieser Code in ein command-in-questionSkript eingebettet werden soll, der Dateiname nicht in den Code eingebettet werden kann. -v list="$opt_x"funktioniert auch nicht wegen der Backslash-Verarbeitung, die awk darauf ausführt. Deshalb verwende ich hier stattdessen ENVIRON.
Stéphane Chazelas
10

grep -n | sort | sed | cut

(   export LC_ALL=C
    grep -n ''   | sort -t:  -nmk1,1 ./L - |
    sed /:/d\;n  | cut  -sd: -f2-
)   <./F

Das sollte ziemlich schnell gehen (einige zeitgesteuerte Tests sind unten aufgeführt) mit Eingaben jeder Größe. Einige Hinweise, wie:

  • export LC_ALL=C
    • Da der Sinn der folgenden Operation darin besteht, die gesamte ./Fgestapelte Datei mit der ./LLineno-Datei in Einklang zu bringen, müssen wir uns nur um ASCII- [0-9]Ziffern und den :Doppelpunkt kümmern .
    • Aus diesem Grund ist es einfacher, diese 11 Zeichen in einem Satz von 128 Möglichkeiten zu finden, als wenn UTF-8 anderweitig betroffen ist.
  • grep -n ''
    • Dies fügt den String LINENO:in den Kopf jeder Zeile in stdin - or ein <./F.
  • sort -t: -nmk1,1 ./L -
    • sortVernachlässigt das Sortieren seiner Eingabedateien überhaupt und geht stattdessen (korrekt) davon aus, dass sie vorsortiert sind, und -mfügt sie in -numericallysortierter Reihenfolge zusammen, wobei grundsätzlich alles ignoriert wird, was über ein eventuell -k1,1vorkommendes -t:Doppelpunkt-Zeichen hinausgeht .
    • Dies kann zwar einige temporäre Speicherplätze erfordern (abhängig davon, wie weit einige Sequenzen voneinander entfernt sind) , erfordert jedoch im Vergleich zu einer geeigneten Sortierung nicht viel und ist sehr schnell, da kein Backtracking erforderlich ist.
    • sortGibt einen einzelnen Stream aus, in dem alle Linenos ./Ldirekt vor den entsprechenden Zeilen stehen ./F. ./LDie Zeilen stehen immer an erster Stelle, weil sie kürzer sind.
  • sed /:/d\;n
    • Wenn die aktuelle Zeile mit einem /:/Doppelpunkt dübereinstimmt, wird sie aus der Ausgabe entfernt. Andernfalls werden die aktuelle und die next-Zeile automatisch gedruckt .
    • Und so sedPflaumen sort‚s Ausgabe nur sequenzielle Leitungspaar , die nicht einen Doppelpunkt und die folgende Zeile passen - oder, um nur eine Zeile aus ./Lund dann die nächsten.
  • cut -sd: -f2-
    • cut -sUppresses von Output die seiner Eingabezeilen, die nicht mindestens einen seiner -d:Elimiter-Strings enthalten - und so werden ./Ldie Zeilen komplett beschnitten.
    • Für die Zeilen, die dies tun, ist ihr erstes durch :Doppelpunkte getrenntes -fFeld cutweg - und so geht es mit allen von ihnen grepeingefügten Linenos.

kleiner Eingangstest

seq 5 | sed -ne'2,3!w /tmp/L
        s/.*/a-z &\& 0-9/p' >/tmp/F

... generiert 5 Zeilen Sample-Input. Dann...

(   export LC_ALL=C; </tmp/F \
    grep -n ''   | sort -t:  -nmk1,1 ./L - |
    sed /:/d\;n  | cut  -sd: -f2-
)|  head - /tmp[FL]

... druckt ...

==> standard input <==
a-z 1& 0-9
a-z 4& 0-9
a-z 5& 0-9

==> /tmp/F <==
a-z 1& 0-9
a-z 2& 0-9
a-z 3& 0-9
a-z 4& 0-9
a-z 5& 0-9

==> /tmp/L <==
1
4
5

größere zeitgesteuerte Tests

Ich habe ein paar ziemlich große Dateien erstellt:

seq 5000000 | tee /tmp/F |
sort -R | head -n1500000 |
sort -n >/tmp/L

... die 5mil Zeilen in /tmp/Fund 1,5mil zufällig ausgewählte Zeilen davon in setzen /tmp/L. Ich habe dann gemacht:

time \
(   export LC_ALL=C
    grep -n ''   | sort -t:  -nmk1,1 ./L - |
    sed /:/d\;n  | cut  -sd: -f2-
)   <./F |wc - l

Es druckte:

1500000
grep -n '' \
    0.82s user 0.05s system 73% cpu 1.185 total
sort -t: -nmk1,1 /tmp/L - \
    0.92s user 0.11s system 86% cpu 1.185 total
sed /:/d\;n \
    1.02s user 0.14s system 98% cpu 1.185 total
cut -sd: -f2- \
    0.79s user 0.17s system 80% cpu 1.184 total
wc -l \
    0.05s user 0.07s system 10% cpu 1.183 total

(Ich habe die Backslashes dort hinzugefügt)

Unter den derzeit hier angebotenen Lösungen ist dies die schnellste von allen, außer einer, wenn sie mit dem oben auf meinem Computer generierten Datensatz verglichen wird. Von den anderen wäre nur einer beinahe um den zweiten Platz gekämpft, und das ist meuhs perl hier .

Dies ist keineswegs die ursprüngliche Lösung, die angeboten wird. Dank der Ratschläge und Anregungen anderer konnte die Ausführungszeit um ein Drittel verkürzt werden. Informationen zu langsameren Lösungen finden Sie im Post-Verlauf (aber warum?) .

Es ist auch erwähnenswert, dass einige andere Antworten möglicherweise besser miteinander konkurrieren, wenn nicht die Multi-CPU-Architektur meines Systems und die gleichzeitige Ausführung der einzelnen Prozesse in dieser Pipeline berücksichtigt würden. Sie arbeiten alle zur gleichen Zeit - jeder auf seinem eigenen Prozessorkern - und geben die Daten weiter und erledigen ihren kleinen Teil des Ganzen. Es ist ziemlich cool.

aber die schnellste lösung ist ...

Aber es ist nicht die schnellste Lösung. Die schnellste Lösung, die hier zweifellos angeboten wird, ist das C-Programm . Ich habe es genannt cselect. Nachdem ich es in meine X-Zwischenablage kopiert habe, habe ich es wie folgt kompiliert:

xsel -bo | cc -xc - -o cselect

Ich habe dann gemacht:

time \
    ./cselect /tmp/L /tmp/F |
wc -l

... und die Ergebnisse waren ...

1500000
./cselect /tmp/L /tmp/F  \
    0.50s user 0.05s system 99% cpu 0.551 total
wc -l \
    0.05s user 0.05s system 19% cpu 0.551 total
mikeserv
quelle
1
Sie können es deutlich schneller machen (fast so schnell wie mein auf Multi-Core - Systemen) mit sed -ne'/:/!{n;p;}' | cut -d: -f2-stattsed -ne'/:/!N;/\n/s/[^:]*://p'
Stéphane Chazelas
@ StéphaneChazelas - Sie könnten bessere Ergebnisse erzielen, wenn Sie seds wechseln - das, was sedich verwende, ist das Erbstück sed- Sie können den aliasWert in den timeErgebnissen sehen. Mein Erbstück-Paket ist übrigens statisch gegen eine musl libc kompiliert - die Regex-Implementierung, für die TRE verwendet wird . Wenn ich es auf GNU umschalte sed- und ohne cutes auszuführen -, erhöht es die Fertigstellungszeit (2,8 Sekunden) um eine volle Sekunde - und addiert es um mehr als ein Drittel. Und das ist nur 0,3 Sekunden schneller als bei Ihnen auf meinem System.
mikeserv
1
sort -mnim gegensatz zu sort -nmk1,1könnte besser sein, da Sie nicht die Aufteilung hier tun müssen (nicht getestet)
Stéphane Chazelas
@ StéphaneChazelas - ja, ich dachte das gleiche und versuchte es auf jede Art und Weise. -nist spezifiziert, nur um die erste numerische Zeichenfolge in einer Zeile zu machen, also dachte ich mir, ok -mnoder -nmund, aus welchem ​​Grund auch immer, das einzige Mal, wenn es in der Abschlusszeit unter 2 Sekunden sank, war, als ich in allen Optionen hinzufügte, wie es ist. Es ist seltsam - und es ist der Grund, warum ich gestern überhaupt nicht darüber nachgedacht habe -m- ich wusste, worum es mir ging, aber es schien nur als eine Art Autooptimierungssache zu funktionieren. Interessanterweise hat das Erbstück sorteine -zString-Länge-Option, die nur für -[cm]... gilt
mikeserv
-nist nicht die erste numerische Zeichenfolge in der Zeile . Es betrachtet die Linie nur als eine Zahl, also abc 123wäre es 0. Es kann also nicht weniger sein effizient sein als mit-t: -k1,1
Stéphane Chazelas
9

Ich würde verwenden awk:

awk 'NR==FNR {a[$1]; next}; FNR in a' L.txt F.txt

Update: Ich habe Leistungsmessungen durchgeführt. Es scheint, dass diese Version bei sehr großen Datenmengen noch besser skaliert (wie es bei den angegebenen Anforderungen der Fall ist), da der Vergleich sehr schnell ist und den zum Aufbau der Hash-Tabelle erforderlichen Aufwand überkompensiert.

Janis
quelle
1
@ Miku; Ja, es ist eine schöne kompakte Lösung. Aber eine Einschränkung; awkMöglicherweise sind nicht alle s in der Lage, mit so großen Datenmengen umzugehen. - Ich benutze GNU awkund es gibt keine Probleme; Der Test mit 500 Millionen Datenzeilen dauerte 7 Minuten.
Janis
1
Dies eher langsam (im Vergleich) real 16m3.468s- user 15m48.447s- sys 0m10.725s. Es wurden 3,3 GB RAM verwendet, um eine 1/10 Größe Lmit 50.000.000 Zeilen zu testen . und Fmit 500.000.000 Zeilen - gegen Zeit für Stéphane Chazelas 'awk anser: real 2m11.637s- user 2m2.748s- sys 0m6.424s- Ich benutze keine schnelle Box, aber der Vergleich ist interessant.
Peter.O
@ Peter.O; Danke für die Daten! Eine langsamere Geschwindigkeit war zu erwarten, da (in meinem eigenen Testfall) eine halbe Milliarde Zeilen in einem assoziativen Array gespeichert waren. (Deshalb habe ich oben für Stephanes Vorschlag "(+1)" kommentiert.) - Obwohl ich erstaunt war, dass diese knappe Lösung immer noch 1 Million Zeilen pro Sekunde verarbeitet! Ich denke, dies macht dieses Codemuster (wegen seiner Einfachheit!) Zu einer praktikablen Option, insbesondere in Fällen mit weniger extremen Datengrößen.
Janis
Es ist definitiv eine praktikable Lösung. Bei den von mir verwendeten Testdaten (5-mil-Linien / 1,5-mil-L) waren Ihre in etwas mehr als 4 Sekunden fertig - nur eine Sekunde hinter Stephanes Antwort. Der Code zum Gen - Testsatz verwendet wird , ist in meiner Antwort, aber es ist meist nur seqausgegeben und dann eine kleine, zufällig ausgewählte Untergruppe von derselben in L .
mikeserv
1
Ich habe gerade weitere Leistungsmessungen mit einer Datendateigröße von 500 Millionen Zeilen und einer Schlüsseldateigröße von 50 Millionen bzw. 50 Millionen Zeilen durchgeführt. 500 Millionen Zeilen mit einer bemerkenswerten Beobachtung. Bei der kleineren Schlüsseldatei sind die Zeiten 4 Minuten (Stephane) vs. 8 Minuten (Janis), während es bei der größeren Schlüsseldatei 19 Minuten (Stephane) vs. 12 Minuten (Janis) sind.
Janis
3

Der Vollständigkeit halber: Wir können das ausgezeichnete awk-Skript in der Antwort von Stéphane Chazelas und das Perl-Skript in der Antwort von kos zusammenführen, ohne jedoch die gesamte Liste im Gedächtnis zu behalten, in der Hoffnung, dass Perl möglicherweise schneller ist als awk. (Ich habe die Reihenfolge der Argumente geändert, um sie mit der ursprünglichen Frage abzugleichen.)

#!/usr/bin/env perl
use strict;

die "Usage: $0 l f\n" if $#ARGV+1 != 2;
open(L,$ARGV[0]) or die "$ARGV[0]: $!";
open(F,$ARGV[1]) or die "$ARGV[1]: $!";

while(my $number = <L>){
    #chop $number;
    while (<F>) {
        if($. == $number){
            print;
            last;
        }
    }
}
meuh
quelle
Dies ist viel schneller als die awk. Es ist ungefähr so ​​schnell wie meins - ich habe beide Tests gerade drei Mal durchgeführt und jedes Mal habe ich meinen 5-mil-Leitungstestsatz in 1,8 ... Sekunden und Ihren 1,9 ... Sekunden bearbeitet. Der getestete Code ist in meiner Antwort, wenn es Sie interessiert, aber der Punkt ist, dass es sehr gut ist. Außerdem ist die Ausgabe korrekt - ich kann die awkArbeit immer noch nicht erledigen ... Trotzdem werden unsere beiden Antworten von FloHimselfs beschämt .
mikeserv
@mikeserv, wir müssen verschiedene awks haben. Auf Ihrer Probe erhalte ich 1,4s mit gawk (4s für Janis '), 0,9s mit mawk, 1,7s mit dieser Perl-Lösung, 2,3s mit kos', 4,5s mit Ihrer (GNU sed) und 1,4s mit Ihrer ( GNU sed) und meine Verbesserungsvorschläge (und 0,5s für die C-Lösung).
Stéphane Chazelas
@ MikeServ, ah! Natürlich macht das Gebietsschema bei Ihrer Herangehensweise einen Unterschied. Abwärts von 4,5 auf 2,3 Sekunden beim Umschalten von UFT-8 auf C.
Stéphane Chazelas
3

Ich habe dazu ein einfaches Perl-Skript geschrieben:

Usage: script.pl inputfile_f inputfile_f

#!/usr/bin/env perl

$number_arguments = $#ARGV + 1;
if ($number_arguments != 2) {
    die "Usage: script.pl inputfile_f inputfile_l\n";
}

open($f, '<', $ARGV[0])
    or die "$ARGV[0]: Not found\n";
open($l, '<', $ARGV[1])
    or die "$ARGV[1]: Not found\n";

@line_numbers = <$l>;

while ($line = <$f>) {
    $count_f ++;
    if ($count_f == @line_numbers[$count_l]) {
        print $line;
        $count_l ++;
    }
}
  • Ladungen F.txt
  • Ladungen L.txt
  • Speichert jede Zeile von L.txtin einem Array
  • Liest F.txtzeilenweise und verfolgt dabei die aktuelle Zeilennummer und den aktuellen Array-Index. erhöht die F.txtaktuelle Zeilennummer; Wenn die F.txtaktuelle Zeilennummer mit dem Inhalt des Arrays am aktuellen Arrayindex übereinstimmt, wird die aktuelle Zeile gedruckt und der Index erhöht

Kosten- und Komplexitätsaspekte :

In Anbetracht der Kosten für die Ausführung der Aufgaben, der Kosten für die Durchführung der Vergleiche und der Kosten für das Drucken der Zeilen, wobei N 1 als Anzahl der Zeilen F.txtund N 2 als Anzahl der Zeilen angegeben wird, läuft L.txtdie whileSchleife höchstens N 1- mal. führt zu 2N 1 + N 2 -Zuweisungen (offensichtlich unter der Annahme, dass N 1 > N 2 ist ), zu 2N 1 -Vergleichen und zu N 2 -Drucken; Wenn die Kosten für jede Operation gleich sind, betragen die Gesamtkosten für die Ausführung der whileSchleife 4N 1 + 2N 2 , was zu einer Komplexität des Skripts von O (N) führt.

Test mit einer Eingabedatei mit 10 Millionen Zeilen :

Verwenden einer 10-Millionen-Zeilen- F.txtDatei mit zufälligen Zeilen von 50 Zeichen Länge und einer 10-Millionen-Zeilen- L.txtDatei mit Zahlen von 1 bis 10000000 (Worst-Case-Szenario):

~/tmp$ for ((i=0; i<3; i++)); do time ./script.pl F.txt L.txt > output; done

real    0m15.628s
user    0m13.396s
sys 0m2.180s

real    0m16.001s
user    0m13.376s
sys 0m2.436s

real    0m16.153s
user    0m13.564s
sys 0m2.304s
kos
quelle
2

Diese Perl-Lösung ist um etwa 20% schneller als die anderen awk- oder perl-Lösungen, jedoch offensichtlich nicht so schnell wie die Lösung in C.

perl -e '
  open L, shift or die $!;
  open F, shift or die $!;
  exit if ! ($n = <L>);
  while (1) {
    $_ = <F>;
    next if $. != $n;
    print;
    exit if ! ($n = <L>);
  }
' -- L F
jrw32982 unterstützt Monica
quelle
0
cat <<! >L.txt
1
3
!

cat <<! >F.txt
Hello World
Hallo Welt
Hola mundo
!

cmd(){
 L=$1 F=$2
 cat -n $F |
 join $L - |
 sed 's/[^ ]* //'
}

cmd L.txt F.txt
Hello World
Hola mundo

Da L.txt sortiert ist, können Sie join verwenden. Nummerieren Sie einfach jede Zeile in F.txt, verbinden Sie die beiden Dateien und entfernen Sie die Zeilennummer. Es werden keine großen Zwischendateien benötigt.

Tatsächlich werden Ihre Datenleitungen durch das oben beschriebene Verfahren entstellt, indem der gesamte Leerraum durch ein einzelnes Leerzeichen ersetzt wird. Um die Zeile intakt zu halten, müssen Sie als Trennzeichen ein Zeichen wählen, das in Ihren Daten nicht vorkommt, z. B. "|". Der cmd ist dann

cmd(){
 L=$1 F=$2
 cat -n $F |
 sed 's/^ *//;s/\t/|/' |
 join -t'|' $L - |
 sed 's/[^|]*|//'
}

Das erste sed entfernt führende Leerzeichen aus der Ausgabe von "cat -n" und ersetzt den Tabulator. Der zweite Satz entfernt die Zeilennummer und "|".

meuh
quelle
Ich befürchte, dass dies bei größeren Dateien nicht funktioniert. Es braucht <10 Zeilen. Ich hatte die gleiche Idee und versuchte es, join L.txt <(nl F.txt )aber es funktioniert nicht bei großen Dateien. Willkommen auf der Seite, es kommt übrigens nicht oft vor, dass wir von neuen Nutzern so klare und gut formatierte Antworten erhalten!
terdon
@terdon, Ja, schade , dass join/ commkann nicht arbeiten mit numerisch sortiert Eingabe.
Stéphane Chazelas
@terdon: Ich bin deiner Spur nachgegangen (jetzt gelöscht) und habe es versucht join -t' ' <(<L.txt awk '{printf("%010s\n",$0)}') <(<F.txt awk '{printf("%010s %s\n",NR,$0)}') | cut -d' ' -f2-- es war langsam! - und selbst wenn ich vorbereitete Dateien mit geeigneten, mit 0 aufgefüllten Schlüsseln eingespeist habe join -t' ' L.txt F.txt | cut -d' ' -f2- , war es immer noch langsam (ohne die Vorbereitungszeit) - langsamer als die awkAntwort von @Janis (wo ich einen Kommentar zu den tatsächlichen Zeiten für beide gepostet habe) seine und @ StéphaneChazelas Antwort
Peter.O
@ Peter.O ja. Ich habe es mit einem ähnlichen Ansatz versucht, bei dem einer der Fehler vermieden wird, aber ich konnte keinen Weg finden, damit es sowohl funktioniert als auch sich lohnt.
terdon
@terdon und andere: Die tatsächliche Zeit für die join+ awk printf Prozessersetzung war real 20m11.663s user 19m35.093s sys 0m10.513s gegen Stéphane Chazelas ' real 2m11.637s user 2m2.748s sys 0m6.424s mit L50 Millionen Zeilen, F500 Millionen Zeilen.
Peter.O
0

Der Vollständigkeit halber noch ein joinLösungsversuch:

sed -r 's/^/00000000000000/;s/[0-9]*([0-9]{15})/\1/' /tmp/L | join <( nl -w15 -nrz /tmp/F ) - | cut -d' ' -f2-

Dies funktioniert, indem die Spalte mit der Zeilennummer, die verbunden wird, als feste Länge mit führenden Nullen formatiert wird, sodass die Zahlen immer 15 Stellen lang sind. Dies umgeht das Problem, dass Verknüpfungen die normale numerische Sortierreihenfolge nicht mögen, da die Spalte nun effektiv als Wörterbuchsortierung verwendet werden muss. nlwird verwendet, um Zeilennummern in diesem Format zu F.txt hinzuzufügen. sedMuss leider zur Neuformatierung der Nummerierung in L.txt verwendet werden.

Dieser Ansatz scheint bei den mit der @ mikeserv-Methode generierten Testdaten in Ordnung zu sein. Aber es ist immer noch sehr langsam - die c-Lösung ist auf meinem Computer 60x schneller. etwa 2/3 der Zeit wird in sedund 1/3 in verbracht join. Vielleicht gibt es einen besseren sed Ausdruck ...

Digitales Trauma
quelle
Ok - aber warum stellen wir alle Nullen voran? Ich versuche ein Gefühl dafür zu bekommen. Außerdem ist es nlsuper cool, aber Sie können es nicht zuverlässig für ungetestete Eingaben verwenden. Eines der Dinge, die es so cool machen, ist der logische Seitenbegrenzer -d . Wenn eine Eingabezeile standardmäßig nur aus den Zeichenfolgen :\` (aber ohne das nachfolgende Grab) 1, 2, 3 oder 3 Mal hintereinander besteht, werden Ihre Zählungen etwas verrückt. Experimentieren Sie mit - es ist ziemlich ordentlich. Sehen Sie sich vor allem an, was passiert, wenn nl` eine Zeile mit 1 Trennzeichen und später eine weitere mit 3 oder 2
mikeserv am
0

Da die akzeptierte Antwort in C ist, dachte ich, es ist in Ordnung, hier eine Python-Lösung zu werfen:

# Read mask
with open('L.txt', 'r') as f:
    mask = [int(line_num) for line_num in f.read().splitlines()]

# Filter input file
filtered_lines = []
with open('F.txt', 'r') as f:
    for i, line in enumerate(f.read().splitlines()):
        if (i+1) in mask:
            filtered_lines.append(line)

# Write newly filtered file
with open('F_filtered.txt', 'w') as f:
    for line in filtered_lines:
        f.write('%s\n' % line)

Wenn Sie eine externe Bibliothek wie numpy verwenden, sieht eine Lösung noch eleganter aus:

import numpy as np

with open('L.txt', 'r') as f:
    mask = np.array([int(line_num)-1 for line_num in f.read().splitlines()])

with open('F.txt', 'r') as f:
    lines = np.array(f.read().splitlines())
filtered_lines = lines[mask]

with open('F_filtered.txt', 'w') as f:
    for line in filtered_lines:
        f.write('%s\n' % line)
AlexG
quelle