Rimuovere tutte le righe nel file A che contengono le stringhe nel file B


15

Ho un file CSV users.csvcon un elenco di nomi utente, ID utente e altri dati:

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 un altro file toremove.txtho un elenco di ID utente:

30923833
77392318

Esiste un modo intelligente ed efficiente per rimuovere tutte le righe dal users.csvfile che contengono gli ID toremove.txt? Ho scritto una semplice app Python per analizzare i due file e scrivere su un nuovo file solo quelle righe che non si trovano in toremove.txt, ma è straordinariamente lento. Forse alcuni sedo la awkmagia possono aiutare qui?

Questo è il risultato desiderato, considerando gli esempi sopra:

username, userid, sidebar_side, sidebar_colour
"John Lennon", 90123412, "left", "blue"
"George Harrison", 72349482, "left", "green"

Forse dovresti condividere il tuo script Python. Ho il sospetto che ci sia qualcosa di sbagliato lì, come essere O (N²) Anche se se stai conservando e rimuovendo milioni di dischi, la magia non ti aiuterà molto.
Ángel

Lo script è in effetti O (n <sup> 2 </sup>): n per le users.csvrighe del file e n per le righe di toremove.txt. Non sono davvero sicuro di come farlo con una complessità inferiore. L'essenza di esso è: for u in users: if not any(toremove in u): outputfile.write(u). Posso pubblicarlo su Code Review.
dotancohen,

1
Vorrei leggere toremove.txt, salvando le voci come chiavi . Iterate users.csv, stampando quelli in cui l'id non è nel dict. Ottieni l'elaborazione O (n) per entrambi toremove.txte users.csv, e l'utilizzo della memoria O (n) per toremove.txt(che è probabilmente relativamente piccolo)
Ángel

@ Ángel: Sì, è esattamente così che funziona la sceneggiatura!
dotancohen,

1
Controllare se esiste una chiave in un dizionario, equivale a un controllo della tabella hash, che è (quasi) O (1). D'altra parte, se deve iterare gli elementi da rimuovere, è O (m)
Ángel

Risposte:


15

Con grep, puoi fare:

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

Con 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"

@terdon: Dang! Stavo per dirlo. Si noti, tuttavia, che la risposta di Gnouc (probabilmente) fa ciò che la domanda richiede , ma potrebbe non essere ciò che l'utente desidera.
Scott,

La awksoluzione è molto sensibile alla formattazione dei file esattamente come mostrato nella domanda. Soprattutto, se un nome è solo una parola / token (cioè non contiene spazi; ad es. "Bono") O è più di due token (cioè contiene più di uno spazio; ad es. "Sir Paul McCartney"), Passerà anche se il corrispondenze userid. Meno ovviamente, la stessa cosa accade se non c'è spazio tra la prima virgola e l'id utente, o se c'è più di uno spazio (ad es "John Lennon", 90123412, ….).
Scott,

@Scott: Sì, è la ragione per cui ho messo la awksoluzione dietrogrep
cuonglm

4

Ecco la awkrisposta di Gnouc , modificata per essere cieca nello spazio:

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

Dal momento che usa solo le virgole (e non gli spazi) come delimitatori, $1is "John Lennon", $2is  90123412(con uno spazio iniziale), ecc. Quindi usiamo gensubper rimuovere qualsiasi numero di spazi iniziali da $2 prima di controllare se (l'id utente) era nel toremove.txtfile.


Potresti essere in grado di fare alcune altre cose intelligenti qui (solo pensando ad alta voce) come analizzare il "pezzo esatto" della stringa che non dovrebbe corrispondere e confrontarlo con l'array associativo, o cosa no.
rogerdpack,

Credo che sia quello che sto facendo. Cosa avevi in ​​mente?
Scott,

Sì, sei tu. Mi riferivo solo a se avessi bisogno di fare qualcosa di più funky come rimuovere la prima metà di una riga o qualcosa del genere (downcasing, ecc. Stackoverflow.com/a/4784647/32453 ) solo analisi specializzata
rogerdpack

0

OK un modo ruby: se hai un elenco di stringhe in un file e vuoi rimuovere tutte le righe da un altro file che contengono anche qualsiasi stringa nel primo file (in questo caso rimuovendo "file2" da "file1") file ruby :

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

sfortunatamente con un grande file "rimuovere" questo sembra degradare la complessità in O (N ^ 2) (la mia ipotesi è che regexp abbia molto lavoro da fare), ma potrebbe comunque essere utile a qualcuno là fuori (se tu desidera più che rimuovere le righe complete). Potrebbe essere più veloce in alcuni casi.

Un'altra opzione se stai andando per la velocità è quella di utilizzare lo stesso meccanismo di controllo dell'hash, ma di "analizzare" attentamente la linea per stringhe che potrebbero corrispondere, quindi confrontandole con il tuo hash.

In ruby, potrebbe apparire così:

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

Vedi anche la risposta di Scott, è simile alle risposte awk qui proposte ed evita la complessità di O (N ^ 2) (phew).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.