Angenommen, ich habe eine Datei (nenne sie sample.txt), die so aussieht:
Row1,10
Row2,20
Row3,30
Row4,40
Ich möchte in der Lage sein, an einem Stream aus dieser Datei zu arbeiten, der im Wesentlichen die paarweise Kombination aller vier Zeilen ist (also sollten wir insgesamt 16 erhalten). Ich suche zum Beispiel nach einem Streaming-Befehl (dh einem effizienten Befehl), bei dem die Ausgabe wie folgt lautet:
Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row1,20 Row2,20
...
Row4,40 Row4,40
Mein Anwendungsfall ist, dass ich diese Ausgabe in einen anderen Befehl (wie awk) streamen möchte, um eine Metrik für diese paarweise Kombination zu berechnen.
Ich habe eine Möglichkeit, dies in awk zu tun, aber meine Sorge ist, dass meine Verwendung des END {} -Blocks bedeutet, dass ich die gesamte Datei vor der Ausgabe im Speicher speichere. Beispielcode:
awk '{arr[$1]=$1} END{for (a in arr){ for (a2 in arr) { print arr[a] " " arr[a2]}}}' samples/rows.txt
Row3,30 Row3,30
Row3,30 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row1,10 Row1,10
Row1,10 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Gibt es eine effiziente Möglichkeit zum Streamen, ohne dass die Datei im Wesentlichen im Speicher gespeichert und dann im END-Block ausgegeben werden muss?
quelle
Antworten:
Gehen Sie wie folgt vor, damit nicht die gesamte Datei in einem Array gespeichert werden muss. Dies ist im Grunde der gleiche Algorithmus wie bei Terdon.
Wenn Sie möchten, können Sie ihm sogar mehrere Dateinamen in der Befehlszeile geben, und es verarbeitet jede Datei unabhängig und verknüpft die Ergebnisse miteinander.
Auf meinem System läuft dies in ungefähr 2/3 der Zeit von Terdons Perl-Lösung.
quelle
Ich bin mir nicht sicher, ob dies besser ist, als es im Speicher zu tun, aber mit einem
sed
,r
das seine Infile für jede Zeile in seiner Infile ausliest, und einem anderen auf der anderen Seite einer Pipe, dieH
alte Leerzeichen mit Eingabezeilen abwechselt ...AUSGABE
Ich habe das anders gemacht. Es speichert einige im Speicher - es speichert eine Zeichenfolge wie:
... für jede Zeile in der Datei.
Es ist sehr schnell. Es ist
cat
die Datei so oft wie es Zeilen in der Datei zu a gibt|pipe
. Auf der anderen Seite der Pipe wird diese Eingabe so oft mit der Datei selbst zusammengeführt, wie sich Zeilen in der Datei befinden.Das
case
Zeug ist nur für die Portabilität -yash
undzsh
beide fügen ein Element zum Split hinzu, währendmksh
undposh
beide eines verlieren.ksh
,dash
,busybox
, Undbash
alle Split, um genau so viele Felder wie es Nullen sind als gedruckte durchprintf
. Wie oben beschrieben, werden auf meinem Computer für alle oben genannten Shells die gleichen Ergebnisse erzielt.Wenn die Datei sehr lang ist, kann es zu
$ARGMAX
Problemen mit zu vielen Argumenten kommen, die Sie in diesem Fall einführen müsstenxargs
oder Ähnliches.Bei der gleichen Eingabe, die ich vor der Ausgabe verwendet habe, ist sie identisch. Aber wenn ich größer würde ...
Das erzeugt eine Datei, die fast mit der zuvor verwendeten identisch ist (ohne Zeile) - aber mit 1000 Zeilen. Sie können selbst sehen, wie schnell es ist:
Bei 1000 Zeilen gibt es einige geringfügige Leistungsunterschiede zwischen den Shells - dies
bash
ist ausnahmslos die langsamste -, aber da sie sowieso nur die Arg-Zeichenfolge (1000 Kopienfilename -
) generieren, ist der Effekt minimal. Der Leistungsunterschied zwischenzsh
- wie oben - undbash
beträgt hier 100stel Sekunden.Hier ist eine andere Version, die für eine Datei beliebiger Länge funktionieren sollte:
Es erstellt einen Softlink zu seinem ersten Argument
/tmp
mit einem halb-zufälligen Namen, damit es nicht an seltsamen Dateinamen hängen bleibt. Das ist wichtig, weilcat
die Argumente über eine Leitung zugeführt werdenxargs
.cat
‚s Ausgabe wird gespeichert ,<&3
währendsed
p
Rints jede Zeile in dem ersten arg so oft wie es Zeilen in dieser Datei ist - und sein Skript wird auch über ein Rohr , um es zugeführt. Wiederpaste
verschmilzt seine Eingabe, aber dieses Mal ist es dauert nur zwei Argumente-
noch einmal für seine Standard - Eingabe und der Linknamen/dev/fd/3
.Das Letzte - der
/dev/fd/[num]
Link - sollte auf jedem Linux-System und vielen anderen funktionieren, aber wenn es keine Named Pipe erstelltmkfifo
und stattdessen verwendet, sollte es auch funktionieren.Als letztes wird
rm
der Softlink erstellt, der vor dem Beenden erstellt wird.Diese Version ist eigentlich noch schneller auf meinem System. Ich vermute, es liegt daran, dass es, obwohl es mehr Anwendungen ausführt, beginnt, ihnen ihre Argumente sofort zu übergeben - wohingegen, bevor es sie alle zuerst stapelte.
quelle
ctrl+v; ctrl+j
um wie ich Zeilenumbrüche zu erhalten.. ./file; fn_name
in diesem Fall kopieren .Nun, Sie könnten es immer in Ihrer Shell tun:
Es ist viel langsamer als Ihre
awk
Lösung (auf meinem Computer dauerte es ~ 11 Sekunden für 1000 Zeilen, verglichen mit ~ 0,3 Sekunden inawk
), aber es speichert immerhin nie mehr als ein paar Zeilen im Speicher.Die obige Schleife funktioniert für die sehr einfachen Daten, die Sie in Ihrem Beispiel haben. Es verschluckt sich an Backslashes und frisst nachgestellte und führende Leerzeichen. Eine robustere Version desselben ist:
Eine andere Möglichkeit ist,
perl
stattdessen Folgendes zu verwenden :Das obige Skript liest jede Zeile der Eingabedatei (
-ln
), speichert sie als$l
, öffnet siesample.txt
erneut und druckt jede Zeile zusammen mit$l
. Das Ergebnis sind alle paarweisen Kombinationen, während immer nur 2 Zeilen gespeichert sind. Auf meinem System dauerte das0.6
auf 1000 Zeilen nur ungefähr Sekunden.quelle
echo
ein Problem sein könnte. Was ich geschrieben hatte (ich fügteprintf
jetzt hinzu ), sollte mit allen richtig funktionieren? Was diewhile
Schleife betrifft, warum? Was ist los mitwhile read f; do ..; done < file
? Sicherlich schlagen Sie keinefor
Schleife vor! Was ist die andere Alternative?Mit
zsh
:$^a
on a Array aktiviert die geschweifte Erweiterung (like in{elt1,elt2}
) für das Array.quelle
Sie können diesen C ++ - Code für recht schnelle Ergebnisse kompilieren .
Bei einer 1000-Zeilen-Datei dauert es ungefähr 0,19 bis 0,27 Sekunden.
Derzeit werden
10000
Zeilen in den Speicher eingelesen (um das Drucken auf dem Bildschirm zu beschleunigen). Wenn Sie1000
Zeichen pro Zeile hätten, würden Sie weniger als benötigen10mb
Speicher ich für ein Problem halten würde. Sie können diesen Abschnitt jedoch vollständig entfernen und ihn direkt auf dem Bildschirm ausdrucken, wenn dies dennoch zu Problemen führt.Sie können kompilieren, indem Sie
g++ -o "NAME" "NAME.cpp"
Wo
NAME
ist der Name der Datei, inNAME.cpp
der sie gespeichert werden soll , und in der Datei, in der dieser Code gespeichert wirdCTEST.cpp:
Demonstration
quelle
Feld 2 ist leer und für alle Elemente in file.txt gleich, sodass
join
jedes Element mit allen anderen verknüpft wird: Es berechnet das kartesische Produkt.quelle
Eine Option mit Python ist die Speicherzuordnung der Datei und die Ausnutzung der Tatsache, dass die Python-Bibliothek für reguläre Ausdrücke direkt mit Dateien mit Speicherzuordnung arbeiten kann. Obwohl dies den Anschein hat, als würden verschachtelte Schleifen über die Datei laufen, stellt die Speicherzuordnung sicher, dass das Betriebssystem den verfügbaren physischen RAM optimal zur Geltung bringt
Alternativ eine schnelle Lösung in Python, obwohl die Speichereffizienz immer noch ein Problem sein könnte
quelle
In bash sollte ksh ebenfalls funktionieren und nur Shell-Built-Ins verwenden:
Beachten Sie, dass hierdurch zwar die gesamte Datei in einer Shell-Variablen gespeichert wird, jedoch nur ein einziger Lesezugriff erforderlich ist.
quelle
sed
Lösung.Erläuterung:
sed 'r file2' file1
- Den gesamten Dateiinhalt von Datei2 für jede Zeile von Datei1 lesen.1~i
bedeutet 1-te Zeile, dann 1 + i Zeile, 1 + 2 * i, 1 + 3 * i usw. Daher1~$((line_num + 1)){h;d}
bedeuteth
alte spitze Zeile zum Puffer,d
lösche Musterraum und beginne neuen Zyklus.'G;s/(.*)\n(.*)/\2 \1/'
-G
Führen Sie für alle Zeilen, mit Ausnahme der im vorherigen Schritt ausgewählten, Folgendes aus: et-Zeile aus dem Haltepuffer und fügen Sie sie an die aktuelle Zeile an. Tauschen Sie dann die Linienstellen aus. Wurdecurrent_line\nbuffer_line\n
, wurdebuffer_line\ncurrent_line\n
Ausgabe
quelle