Entfernen Sie alle Zeilen in Datei A, die die Zeichenfolgen in Datei B enthalten

15

Ich habe eine CSV-Datei users.csvmit einer Liste von Benutzernamen, Benutzer-IDs und anderen Daten:

username, userid, sidebar_side, sidebar_colour
"John Lennon", 90123412, "left", "blue"
"Paul McCartny", 30923833, "left", "black"
"Ringo Starr", 77392318, "right", "blue"
"George Harrison", 72349482, "left", "green"

In einer anderen Datei habe toremove.txtich eine Liste von Benutzer-IDs:

30923833
77392318

Gibt es eine clevere und effiziente Möglichkeit, alle Zeilen aus der users.csvDatei zu entfernen, in der sich die IDs befinden toremove.txt? Ich habe eine einfache Python-App geschrieben, um die beiden Dateien zu analysieren und in eine neue Datei nur die Zeilen zu schreiben, in denen sie nicht gefunden werden toremove.txt, aber sie ist außerordentlich langsam. Vielleicht kann etwas sedoder awkMagie hier helfen?

Dies ist das gewünschte Ergebnis, wenn man die obigen Beispiele betrachtet:

username, userid, sidebar_side, sidebar_colour
"John Lennon", 90123412, "left", "blue"
"George Harrison", 72349482, "left", "green"
dotancohen
quelle
Vielleicht solltest du dein Python-Skript teilen. Ich vermute, da stimmt etwas nicht, wie O (N²) zu sein. Auch wenn Sie Millionen von Aufzeichnungen behalten und entfernen, hilft Magie nicht allzu viel.
Ángel
Das Skript ist in der Tat O (n <sup> 2 </ sup>): n für die users.csvZeilen der Datei und n für die Zeilen von toremove.txt. Ich bin mir nicht sicher, wie ich es mit geringer Komplexität machen soll. Der Kern ist: for u in users: if not any(toremove in u): outputfile.write(u). Ich kann es bei Code Review posten.
Dotancohen
1
Ich würde lesen toremove.txtund die Einträge als Schlüssel speichern . Iterieren Sie users.csv und geben Sie diejenigen aus, bei denen die ID nicht im Diktat enthalten ist. Sie erhalten O (n) Verarbeitung für beide toremove.txtund users.csv, und O (n) Speichernutzung für toremove.txt(was wahrscheinlich ist relativ klein)
Ángel
@ Ángel: Ja, genau so funktioniert das Skript!
Dotancohen
1
Das Überprüfen, ob ein Schlüssel in einem Wörterbuch vorhanden ist, entspricht einer Hash-Tabellenprüfung, die (fast) 0 (1) ist. Wenn es andererseits erforderlich ist, die zu entfernenden Elemente zu iterieren, ist dies O (m)
Ángel

Antworten:

15

Mit grepkönnen Sie:

$ grep -vwF -f toremove.txt users.txt 
username, userid, sidebar_side, sidebar_colour
"John Lennon", 90123412, "left", "blue"
"George Harrison", 72349482, "left", "green"

Mit awk:

$ awk -F'[ ,]' 'FNR==NR{a[$1];next} !($4 in a)' toremove.txt users.txt 
username, userid, sidebar_side, sidebar_colour
"John Lennon", 90123412, "left", "blue"
"George Harrison", 72349482, "left", "green"
cuonglm
quelle
@terdon: Mist! Ich wollte das sagen. Beachten Sie jedoch, dass die Antwort von Gnouc (wohl) genau das tut, was die Frage verlangt , aber möglicherweise nicht das ist, was der Benutzer will.
Scott
Die awkLösung reagiert sehr empfindlich darauf, dass die Dateien genau wie in der Frage gezeigt formatiert werden . Am augenfälligsten ist es, wenn ein Name nur ein Wort / Token ist (dh, er enthält keine Leerzeichen; z. B. "Bono") oder mehr als zwei Token (dh, er enthält mehr als ein Leerzeichen; z. B. "Sir Paul McCartney"), selbst wenn das durchlaufen wird Benutzer-ID-Übereinstimmungen. Weniger offensichtlich passiert dasselbe, wenn zwischen dem ersten Komma und der Benutzer-ID kein Leerzeichen ist oder wenn mehr als ein Leerzeichen (z "John Lennon", 90123412, …. B. ) vorhanden ist.
Scott
@Scott: Ja, das ist der Grund, warum ich die awkLösung hinter mich gebracht habegrep
cuonglm
4

Hier ist Gnoucs awkAntwort:

awk -F, 'FNR==NR{a[$1];next} !(gensub("^ *","",1,$2) in a)' toremove.txt users.csv

Da es nur Kommas (und keine Leerzeichen) als Begrenzer verwendet, $1ist "John Lennon", $2ist  90123412(mit einem führenden Leerzeichen) usw. Daher gensubentfernen wir eine beliebige Anzahl führender Leerzeichen, $2 bevor wir prüfen, ob es (die Benutzer-ID) in der toremove.txtDatei war.

Scott
quelle
Vielleicht können Sie hier auch andere clevere Dinge tun (nur laut nachdenken), z. B. das "genaue Stück" der Zeichenfolge, das nicht übereinstimmen sollte, analysieren und das mit dem assoziativen Array vergleichen, oder was nicht.
Rogerdpack
Ich glaube, das mache ich. Was hattest du im Sinn?
Scott
Ja, das bist Du. Ich bezog mich nur darauf, ob Sie etwas Funkigeres tun mussten, wie das Entfernen der ersten Hälfte einer Zeile oder etwas Ähnliches (Downcasing usw. stackoverflow.com/a/4784647/32453 ), nur spezialisiertes Parsen
Rogerdpack 16.10.15
0

OK ein Rubin Art und Weise: wenn Sie eine Liste von Zeichenkette in einer Datei, und Sie alle Zeilen aus einer anderen Datei zu entfernen , dass auch enthält eine beliebige Zeichenfolge in der ersten Datei (in diesem Fall „file2“ von „file1“ zu entfernen) Rubin - Datei :

b=File.read("file2").split # subtract this one out
remove_regex = Regexp.new(b.join('|'))
File.open("file1", "r").each_line do |line|
  if line !~ remove_regex
    puts line
  end
end

Leider scheint dies bei einer großen "zu entfernenden" Datei die Komplexität auf O (N ^ 2) herabzusetzen (meine Vermutung ist, dass RegExp eine Menge Arbeit zu erledigen hat), könnte aber dennoch für jemanden da draußen nützlich sein (falls Sie dies tun) wollen mehr als das Entfernen ganzer Zeilen). In bestimmten Fällen kann es schneller sein.

Eine andere Möglichkeit, wenn Sie auf Geschwindigkeit aus sind, besteht darin, denselben Hash-Überprüfungsmechanismus zu verwenden, die Zeile jedoch sorgfältig nach möglicherweise übereinstimmenden Zeichenfolgen zu "parsen" und sie dann mit Ihrem Hash zu vergleichen.

In Ruby könnte das so aussehen:

b=File.read("file2").split # subtract this one out
hash={}
for line in b
  hash[line] = 1
end

ARGF.each_line do |line|
  ok = true
  for number in line.scan(/\d{9}/)
    if hash.key? number
      ok=false
    end
  end
  if (ok)
    puts line
  end
end

Siehe auch Scotts Antwort, die den hier vorgeschlagenen awk-Antworten ähnlich ist, und vermeidet die Komplexität von O (N ^ 2) (puh).

Rogerdpack
quelle