Ich habe zwei Dateien A
- nodes_to_delete
und B
- nodes_to_keep
. Jede Datei hat viele Zeilen mit numerischen IDs.
Ich möchte die Liste der numerischen IDs haben, die in, nodes_to_delete
aber NICHT in sind nodes_to_keep
, z
.
Dies in einer PostgreSQL-Datenbank zu tun, ist unangemessen langsam. Gibt es eine gute Möglichkeit, dies mit Linux CLI-Tools in Bash zu tun?
UPDATE: Dies scheint ein Pythonic-Job zu sein, aber die Dateien sind wirklich sehr, sehr groß. Ich habe einige ähnliche Probleme gelöst mit uniq
, sort
und einige Mengenlehre Techniken. Dies war ungefähr zwei oder drei Größenordnungen schneller als die Datenbankäquivalente.
bash
file-io
set-difference
Adam Matan
quelle
quelle
Antworten:
Der Befehl comm macht das.
quelle
sort
zuerst.join(1)
wäre mir zB unbekannt geblieben.Jemand hat mir vor ein paar Monaten gezeigt, wie man genau das macht, und dann konnte ich es eine Weile nicht finden ... und beim Schauen bin ich auf Ihre Frage gestoßen. Hier ist es :
set_union () { sort $1 $2 | uniq } set_difference () { sort $1 $2 $2 | uniq -u } set_symmetric_difference() { sort $1 $2 | uniq -u }
quelle
comm
ist nicht in allen Umgebungen verfügbar.set_difference
undset_symmetric_difference
funktioniert nicht immer richtig - sie löschen Zeilen, die für die erste Eingabedatei eindeutig sind, wenn diese Zeilen in dieser Datei nicht eindeutig sind.Verwendung
comm
- Es werden zwei sortierte Dateien Zeile für Zeile verglichen.Die kurze Antwort auf Ihre Frage
Dieser Befehl gibt Zeilen zurück, die nur für deleteNodes und nicht für keepNodes gelten.
Beispieleinrichtung
Erstellen wir die Dateien mit dem Namen
keepNodes
unddeleteNodes
und verwenden sie als unsortierte Eingabe für dencomm
Befehl.$ cat > keepNodes <(echo bob; echo amber;) $ cat > deleteNodes <(echo bob; echo ann;)
Wenn Sie comm ohne Argumente ausführen, werden standardmäßig 3 Spalten mit diesem Layout gedruckt:
Führen Sie in unseren obigen Beispieldateien comm ohne Argumente aus. Beachten Sie die drei Spalten.
Spaltenausgabe unterdrücken
Unterdrücke Spalte 1, 2 oder 3 mit -N; Beachten Sie, dass beim Ausblenden einer Spalte das Leerzeichen kleiner wird.
Sortieren ist wichtig!
Wenn Sie comm ausführen, ohne die Datei zuerst zu sortieren, schlägt dies ordnungsgemäß mit einer Meldung fehl, welche Datei nicht sortiert ist.
comm: file 1 is not in sorted order
quelle
deleteNodes
, die nicht vorhanden sindkeepNodes
), wären jedoch besser, wenn die richtige Lösung hervorgehoben würde :comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
.comm
wurde speziell für diese Art von Anwendungsfall entwickelt, erfordert jedoch sortierte Eingaben.awk
ist wohl ein besseres Werkzeug dafür, da es ziemlich einfach ist, Mengenunterschiede zu finden, dies nicht erfordertsort
und zusätzliche Flexibilität bietet.awk 'NR == FNR { a[$0]; next } !($0 in a)' nodes_to_keep nodes_to_delete
Vielleicht möchten Sie zum Beispiel nur den Unterschied in Zeilen finden, die nicht negative Zahlen darstellen:
awk -v r='^[0-9]+$' 'NR == FNR && $0 ~ r { a[$0] next } $0 ~ r && !($0 in a)' nodes_to_keep nodes_to_delete
quelle
Vielleicht brauchen Sie einen besseren Weg, um es in Postgres zu tun. Ich kann ziemlich wetten, dass Sie keinen schnelleren Weg finden, dies mit Flatfiles zu tun. Sie sollten in der Lage sein, einen einfachen inneren Join durchzuführen und davon auszugehen, dass beide ID-Spalten indiziert sind, was sehr schnell sein sollte.
quelle
explain
unterstützt Ihre Behauptung, aber es funktioniert einfach nicht für sehr große (~ zig Millionen) Tabellen.Dies unterscheidet sich also geringfügig von den anderen Antworten. Ich kann nicht sagen, dass ein C ++ - Compiler genau ein "Linux CLI-Tool" ist, aber das Ausführen
g++ -O3 -march=native -o set_diff main.cpp
(mit dem folgenden Codemain.cpp
kann den Trick machen):#include<algorithm> #include<iostream> #include<iterator> #include<fstream> #include<string> #include<unordered_set> using namespace std; int main(int argc, char** argv) { ifstream keep_file(argv[1]), del_file(argv[2]); unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()}; string line; while (getline(del_file, line)) { init_lines.erase(line); } copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n")); }
Zur Verwendung einfach ausführen
set_diff B A
( nichtA B
, daB
istnodes_to_keep
) und der resultierende Unterschied wird auf stdout gedruckt.Beachten Sie, dass ich auf einige bewährte Methoden für C ++ verzichtet habe, um den Code einfacher zu halten.
Es könnten viele zusätzliche Geschwindigkeitsoptimierungen vorgenommen werden (zum Preis von mehr Speicher).
mmap
wäre auch besonders nützlich für große Datenmengen, aber das würde den Code viel komplizierter machen.Da Sie erwähnt haben, dass die Datenmengen groß sind, hielt ich das Lesen
nodes_to_delete
einer Zeile gleichzeitig für eine gute Idee, um den Speicherverbrauch zu reduzieren. Der im obigen Code verfolgte Ansatz ist nicht besonders effizient, wenn sich viele Dupes in Ihrem befindennodes_to_delete
. Auch die Ordnung bleibt nicht erhalten.Etwas, das einfacher zu kopieren und einzufügen ist
bash
(dh die Erstellung von überspringtmain.cpp
):g++ -O3 -march=native -xc++ -o set_diff - <<EOF #include<algorithm> #include<iostream> #include<iterator> #include<fstream> #include<string> #include<unordered_set> using namespace std; int main(int argc, char** argv) { ifstream keep_file(argv[1]), del_file(argv[2]); unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()}; string line; while (getline(del_file, line)) { init_lines.erase(line); } copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n")); } EOF
quelle