Hapus semua baris dalam file A yang berisi string dalam file B

15

Saya memiliki file CSV users.csvdengan daftar Nama pengguna, ID pengguna, dan data lainnya:

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"

Di file lain toremove.txtsaya punya daftar ID pengguna:

30923833
77392318

Apakah ada cara pintar dan efisien untuk menghapus semua baris dari users.csvfile yang berisi ID toremove.txt? Saya telah menulis aplikasi Python sederhana untuk mem-parsing dua file dan menulis ke file baru hanya baris-baris yang tidak ditemukan toremove.txt, tetapi ini sangat lambat. Mungkin beberapa sedatau awksihir dapat membantu di sini?

Ini adalah hasil yang diinginkan, dengan mempertimbangkan contoh di atas:

username, userid, sidebar_side, sidebar_colour
"John Lennon", 90123412, "left", "blue"
"George Harrison", 72349482, "left", "green"
dotancohen
sumber
Mungkin Anda harus membagikan skrip python Anda. Saya menduga ada sesuatu yang salah di sana, seperti menjadi O (N²) Meskipun jika Anda menyimpan dan menghapus jutaan rekaman, sihir tidak akan banyak membantu.
Ángel
Script sebenarnya adalah O (n <sup> 2 </sup>): n untuk baris users.csvfile, dan n untuk baris toremove.txt. Saya tidak begitu yakin bagaimana melakukannya dengan kompleksitas yang lebih rendah. Inti dari itu adalah: for u in users: if not any(toremove in u): outputfile.write(u). Saya dapat mempostingnya ke Code Review.
dotancohen
1
Saya akan membaca toremove.txt, menyimpan entri sebagai kunci . Iterate users.csv, cetak di mana id tidak ada dalam dikt. Anda mendapatkan pemrosesan O (n) untuk keduanya toremove.txtdan users.csv, dan O (n) penggunaan memori untuk toremove.txt(yang mungkin relatif kecil)
Ángel
@ Ángel: Ya, itulah cara kerja skrip!
dotancohen
1
Memeriksa apakah kunci ada dalam kamus, sama dengan cek tabel hash, yaitu (hampir) O (1). Di sisi lain, jika perlu mengulang item yang akan dihapus, itu O (m)
Ángel

Jawaban:

15

Dengan grep, Anda dapat melakukan:

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

Dengan 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
sumber
@terdon: Dang! Saya akan mengatakan itu. Catatan, meskipun, bahwa jawaban Gnouc ini (bisa dibilang) melakukan apa pertanyaannya meminta , tetapi mungkin tidak apa yang diinginkan pengguna.
Scott
The awksolusi adalah sangat sensitif terhadap file yang diformat persis seperti yang ditunjukkan dalam pertanyaan. Paling mencolok, jika nama hanya satu kata / token (yaitu, tidak mengandung spasi; misalnya, "Bono") atau lebih dari dua token (yaitu, mengandung lebih dari satu spasi; misalnya, "Sir Paul McCartney"), itu akan melewati bahkan jika kecocokan userid. Kurang jelas, hal yang sama terjadi jika tidak ada ruang antara koma pertama dan userid, atau jika ada lebih dari satu ruang (misalnya, "John Lennon", 90123412, …).
Scott
@Scott: Ya, itulah alasan saya memberikan awksolusigrep
cuonglm
4

Inilah awkjawaban Gnouc , yang dimodifikasi menjadi buta-ruang:

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

Karena hanya menggunakan koma (dan bukan spasi) sebagai pembatas, $1is "John Lennon", $2is  90123412(dengan spasi terdepan), dll. Jadi kami menggunakan gensubuntuk menghapus sejumlah spasi terkemuka $2 sebelum kita memeriksa apakah (userid) ada di toremove.txtfile.

Scott
sumber
Anda mungkin dapat melakukan beberapa hal pintar lainnya di sini (hanya berpikir keras) seperti mengurai "bagian persis" dari string yang tidak cocok, dan membandingkannya dengan array asosiatif, atau apa yang tidak.
rogerdpack
Saya percaya itulah yang saya lakukan. Apa yang ada dalam pikiranmu?
Scott
Ya, kamu. Aku hanya mengacu jika Anda harus melakukan sesuatu lebih funky seperti menghapus paruh pertama garis atau sesuatu seperti itu (downcasing, dll stackoverflow.com/a/4784647/32453 ) hanya khusus parsing
rogerdpack
0

OK cara ruby: jika Anda memiliki daftar string dalam file, dan Anda ingin menghapus semua baris dari file lain yang bahkan mengandung string apa pun di file pertama (dalam hal ini menghapus "file2" dari "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

sayangnya dengan file "untuk menghapus" besar ini tampaknya menurunkan kompleksitas-bijaksana untuk O (N ^ 2) (asumsi saya adalah regexp memiliki banyak pekerjaan yang harus dilakukan), tetapi masih mungkin berguna bagi seseorang di luar sana (jika Anda ingin lebih dari menghapus garis penuh). Mungkin lebih cepat dalam kasus-kasus tertentu.

Pilihan lain jika Anda ingin kecepatan adalah menggunakan mekanisme pemeriksaan hash yang sama, tetapi untuk "mengurai" garis untuk string yang mungkin cocok, kemudian membandingkannya dengan hash Anda.

Dalam ruby, mungkin terlihat seperti ini:

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

Lihat juga jawaban Scott, mirip dengan jawaban awk yang diajukan di sana, dan menghindari kompleksitas O (N ^ 2) (phew).

rogerdpack
sumber