A dosyasındaki B dosyasındaki dizeleri içeren tüm satırları kaldır


15

users.csvUserNames, userIDs ve diğer verilerin bir listesini içeren bir CSV dosyası var :

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"

Başka bir dosyada kullanıcı toremove.txtkimliklerinin bir listesi var:

30923833
77392318

users.csvKimlikleri içeren dosyadaki tüm satırları kaldırmanın akıllı ve etkili bir yolu var mı toremove.txt? İki dosyayı ayrıştırmak ve yeni bir dosyaya sadece bulunmayan satırları yazmak için basit bir Python uygulaması yazdım toremove.txt, ancak olağanüstü yavaş. Belki biraz sedya da awksihir burada yardımcı olabilir mi?

Yukarıdaki örnekler göz önünde bulundurularak istenen sonuç budur:

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

Belki python betiğinizi paylaşmalısınız. Ben orada O (N²) gibi yanlış bir şey olduğundan şüpheleniyorum Eğer milyonlarca kayıt tutuyor ve kaldırırsanız sihir çok yardımcı olmaz.
Ángel

Komut dosyası aslında O (n <sup> 2 </sup>): n, users.csvdosyanın satırları için n ve toremove.txt. Daha düşük karmaşıklıkla nasıl yapılacağından emin değilim. Bunun özü şudur: for u in users: if not any(toremove in u): outputfile.write(u). Kod İncelemesine gönderebilirim.
dotancohen

1
Ben okurdum toremove.txtolarak girdilerini tasarruf tuşları . İd.içinde olmayanları yazdırarak users.csv dosyasını yineleyin. Sen her ikisi için işleme O (n) almak toremove.txtve users.csvve için O (n) bellek kullanımı toremove.txt(muhtemelen nispeten küçük)
Ángel

@ Ángel: Evet, senaryo tam da böyle çalışıyor!
dotancohen

1
Sözlükte bir anahtar olup olmadığını kontrol etmek, (neredeyse) O (1) olan bir karma tablo denetimine eşittir. Öte yandan, kaldırılacak öğeleri tekrarlaması gerekiyorsa, bu O (m)
Ángel

Yanıtlar:


15

İle grepşunları yapabilirsiniz:

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

İle 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! Onu söyleyecektim. Not olsa da, Gnouc cevabı (tartışmalı) yaptığı soru ne sorar , ancak kullanıcı istediği olmayabilir.
Scott

awkSolüsyon dosyaları biçimlendirilmiş olması son derece duyarlıdır tam olarak söz konusu gösterilen. En göze çarpan bir şekilde, bir isim sadece bir kelime / jetonsa (yani boşluk içermiyorsa; örneğin, "Bono") veya ikiden fazla jeton (yani, birden fazla boşluk içeriyorsa; örneğin, "Sir Paul McCartney"), kullanıcı kimliği eşleşmeleri. Daha azı belli ki, ilk virgül ile kullanıcı kimliği arasında boşluk yoksa veya birden fazla boşluk varsa (ör "John Lennon", 90123412, ….) Aynı şey olur .
Scott

@Scott: Evet, awkarkasına çözüm grep
koymamın

4

İşte awkboşluk kör olacak şekilde değiştirilmiş Gnouc'un cevabı:

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

O, sınırlayıcı olarak sadece virgül (ve boşluk) kullandığından $1olduğunu "John Lennon", $2olup  90123412kullandığımız Yani vb (lider boşluk) gensubgiden boşluk herhangi bir sayıda kaldırmak için $2 biz (kullanıcı kimliği) içinde olup olmadığını kontrol etmeden önce toremove.txtdosyanın.


Burada, eşleşmemesi gereken dizenin "tam parçasını" ayrıştırmak ve bunu ilişkilendirilebilir dizi ile karşılaştırmak veya neyi karşılaştırmamak gibi başka akıllı şeyler de yapabilirsiniz (sadece yüksek sesle düşünerek).
rogerdpack

Yaptığım şeyin bu olduğuna inanıyorum. Aklında ne var?
Scott

Evet öylesin. Sadece bir hattın ilk yarısını veya bunun gibi bir şeyi (downcasing, vb. ) Kaldırmak gibi daha korkak bir şey yapmanız gerekiyorsa atıfta bulunuyordum stackoverflow.com/a/4784647/32453 ) sadece uzman ayrıştırma
rogerdpack

0

Tamam yakut bir yol: bir dosyada dizelerin listesi varsa ve ilk dosyada herhangi bir dize içeren başka bir dosyadaki tüm satırları kaldırmak istiyorsanız (bu durumda "dosya2" "dosya1" den kaldırılır) yakut dosya :

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

ne yazık ki büyük bir "kaldırmak" dosyası ile bu karmaşıklığı bilge O (N ^ 2) aşağılamak gibi görünüyor (benim varsayım regexp yapacak çok iş var), ama yine de orada birisi için yararlı olabilir (eğer tam satırları kaldırmaktan daha fazlasını isteyin). Bazı durumlarda daha hızlı olabilir.

Hız için gidiyorsanız başka bir seçenek de aynı karma kontrol mekanizmasını kullanmak, ancak eşleşebilecek dizeleri dikkatlice "ayrıştırmak" ve daha sonra bunları karma ile karşılaştırmaktır.

Ruby'de şöyle görünebilir:

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

Ayrıca bkz. Scott'ın cevabı, şimdiye kadar önerilen garip cevaplara benzer ve O (N ^ 2) karmaşıklığından (vay be) kaçınır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.