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


15

users.csvuserNames、userIDs、およびその他のデータのリストを含む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には、ユーザーIDのリストがあります。

30923833
77392318

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

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

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

たぶん、Pythonスクリプトを共有する必要があります。O(N²)のように何か問題があると思いますが、何百万ものレコードを保持して削除する場合、魔法はあまり役に立ちません。
アンヘル14

スクリプトは、実際にはO(n <sup> 2 </ sup>)です。users.csvファイルの行の場合はn、の行の場合はnですtoremove.txt。複雑さを抑えてそれを行う方法はよくわかりません。その要点は次のとおりfor u in users: if not any(toremove in u): outputfile.write(u)です。Code Reviewに投稿できます。
dotancohen

1
私は読んでいましたtoremove.txtとして、入力した内容を保存し、キー。users.csvを繰り返し、idが辞書にないものを出力します。あなたは、両方のための処理をO(n)を取得toremove.txtしてusers.csv、およびのためのO(n)のメモリ使用量toremove.txt(おそらく比較的小さい)
アンヘル・

@アンゲル:はい、それがまさにスクリプトの仕組みです!
dotancohen

1
キーがディクショナリに存在するかどうかを確認することは、ハッシュテーブルチェックに相当します。これは(ほぼ)O(1)です。一方、それは削除するアイテムを反復処理する必要がある場合、それはO(M)だ
アンヘル・

回答:


15

では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"

@terdon:ダン!私はそれを言うつもりでした。ただし、Gnoucの答えは(ほぼ間違いなく)質問が求めるものを実行しますが、ユーザーが望んでいるものではない場合があります。
スコット14

awk解決策は、フォーマットされているファイルに非常に敏感である丁度疑問に示すように。最も明白なのは、名前が1つの単語/トークン(つまり、スペースを含まない、たとえば"Bono")である場合、または3つ以上のトークン(つまり、複数のスペースを含む、たとえば、"Sir Paul McCartney")である場合、ユーザーIDが一致します。それほど明らかではありませんが、最初のコンマとユーザーIDの間にスペースがない場合、または複数のスペース(例:)がある場合にも同じことが起こります"John Lennon", 90123412, …
スコット14

@Scott:はい、それは私が入れた理由ですawk後ろソリューションをgrep
cuonglm

4

Gnoucのawk答えは、スペースブラインドになるように修正されています。

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

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


ここでは、一致しないはずの文字列の「正確な部分」を解析し、それを連想配列と比較するなど、他の賢いことを(大声で考えて)行うことができます。
-rogerdpack

それが私がやっていることだと思います。何を思っていたんだ?
スコット

はい、そうです。行の前半またはそのような何かを削除するなど、もっとファンキーなことをする必要がある場合(ダウンケースなどstackoverflow.com/a/4784647/32453)に特化した解析
-rogerdpack

0

ルビーの方法でOK:ファイルに文字列のリストがあり、最初のファイルに文字列を含むすべての行を別のファイルから削除する場合(この場合、「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)に低下するように見えます(私の推測では、正規表現には多くの作業があります)。行全体を削除するだけではありません)。場合によっては高速になる可能性があります。

速度を上げる場合のもう1つのオプションは、同じハッシュチェックメカニズムを使用することですが、一致する可能性のある文字列の行を慎重に「解析」し、それらをハッシュと比較します。

ルビーでは、次のようになります。

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.