web-dev-qa-db-ja.com

ファイルBの文字列を含むファイルAのすべての行を削除します

UserNames、userID、その他のデータのリストを含むCSVファイルusers.csvがあります。

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"

別のファイルtoremove.txtに、userIDのリストがあります。

30923833
77392318

users.csvのIDを含むtoremove.txtファイルからすべての行を削除するための賢明で効率的な方法はありますか? 2つのファイルを解析し、toremove.txtにない行だけを新しいファイルに書き込む単純なPythonアプリを記述しましたが、非常に遅いです。おそらくいくつかのsedまたはawk魔法がここで役立ちますか?

これは、上記の例を考慮すると、望ましい結果です。

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

grepを使用すると、次のことができます。

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

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"
15
cuonglm

スペースブラインドになるように変更されたGnoucのawkの回答は次のとおりです。

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

区切り文字としてカンマ(スペースではなく)のみを使用しているため、$1"John Lennon"$2 90123412(先頭にスペースを含む)などです。したがって、gensubは、$2ファイルにユーザーIDが含まれているかどうかを確認する前に、toremove.txtから任意の数の先行スペースを削除します。

4
Scott

OK Ruby方法:ファイルに文字列のリストがあり、別のファイルからすべての行を削除したい場合contain最初の文字列ファイル(この場合、「file1」から「file2」を削除)Ruby file:

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

残念ながら、大きな「削除」ファイルを使用すると、これは複雑さの点でO(N ^ 2)に低下するように見えます(私の仮定は、正規表現にはやらなければならない作業がたくさんあることです)。完全な行を削除するだけではありません)。場合によってはより高速になることがあります。

速度を上げる場合の別のオプションは、同じハッシュチェックメカニズムを使用することですが、一致する可能性のある文字列の行を注意深く「解析」し、ハッシュと比較します。

Rubyでは、次のようになります。

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

スコットの回答も参照してください。これは、これまでに提案されたawkの回答に類似しており、O(N ^ 2)の複雑さを回避します(phew)。

0
rogerdpack