파일 B의 문자열을 포함하는 파일 A의 모든 행을 제거하십시오.


15

users.csvuserNames, userID 및 기타 데이터 목록 이있는 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.csvID가 포함 된 파일 에서 모든 행을 제거하는 영리하고 효율적인 방법이 toremove.txt있습니까? 두 파일을 구문 분석하고에없는 행만 새 파일에 쓰도록 간단한 Python 앱을 작성 toremove.txt했지만 매우 느립니다. 아마도 일부 sed또는 awk마술이 여기에 도움이 될 수 있습니까?

위의 예를 고려하면 원하는 결과입니다.

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

파이썬 스크립트를 공유해야 할 수도 있습니다. O (N²)와 같은 문제가 있다고 생각합니다. 수백만 개의 레코드를 유지하고 제거하면 마술이 그다지 도움이되지 않습니다.
Ángel

스크립트는 실제로 O (n <sup> 2 </ sup>)입니다. users.csv파일의 행은 n이고의 행은 n입니다 toremove.txt. 복잡성을 낮추는 방법을 잘 모르겠습니다. 그것의 요지는 : for u in users: if not any(toremove in u): outputfile.write(u). 코드 검토에 게시 할 수 있습니다.
dotancohen

1
toremove.txt항목을 키로 저장하여 읽습니다 . users.csv를 반복하여 id가 dict에없는 것을 인쇄합니다. toremove.txtand에 대한 O (n) 처리 users.csv및 ( toremove.txt아마도 비교적 작은) O (n) 메모리 사용
Ángel

@ Ángel : 그렇습니다. 이것이 바로 스크립트가 작동하는 방식입니다!
dotancohen

1
키가 사전에 있는지 확인하는 것은 해시 테이블 확인 (거의) O (1)와 같습니다. 반면에 제거 할 항목을 반복해야하는 경우 O (m)
Ángel

답변:


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의 답변은 (질문의 여지없이) 질문에서 요구하는 것을 수행 하지만 사용자가 원하는 것이 아닐 수도 있습니다.
Scott

awk솔루션은 질문에 표시된 대로 파일의 형식을 정확하게 지정 합니다 . 가장 눈에 띄게, 이름이 단지 하나의 단어 / 토큰 (즉, 공백을 포함하지 않는 "Bono"경우 (예 :) 또는 둘 이상의 토큰 (즉, 공백을 포함하는 경우 (예 :) "Sir Paul McCartney") 인 경우) 사용자 ID가 일치합니다. 덜 분명하게도, 첫 번째 쉼표와 사용자 ID 사이에 공백이 없거나 둘 이상의 공백이있는 경우 (예 :) 동일한 문제가 발생합니다 "John Lennon", 90123412, ….
Scott

@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(선행 공백 포함) 등으로 사용 gensub하기 $2 때문에 toremove.txt파일 에 사용자 이름이 있는지 여부를 확인하기 전에 여러 개의 선행 공백을 제거하는 데 사용 합니다.


일치하지 않아야하는 문자열의 "정확한 부분"을 구문 분석하고 연관 배열과 비교하거나 그렇지 않은 것과 같은 다른 영리한 작업을 여기에서 할 수 있습니다.
rogerdpack

나는 그것이 내가하고있는 것이라고 믿습니다. 무엇을 염두에 두셨습니까?
Scott

네 그렇습니다. 난 그냥 당신이 그 (downcasing 등 같은 라인 또는 아무것도 상반기 제거 같은 것을 더 펑키을 할 필요가있는 경우에 언급했다 stackoverflow.com/a/4784647/32453를 ) 단지 전문 분석
rogerdpack

0

루비 방식으로 확인 : 파일에 문자열 목록이 있고 첫 번째 파일에 문자열이 포함 된 다른 파일에서 모든 줄을 제거하려는 경우 (이 경우 "file1"에서 "file2"제거) 루비 파일 :

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)로 저하시키는 것처럼 보입니다 (제 생각에는 정규 표현식에 많은 작업이 필요합니다). 전체 줄을 제거하는 것 이상을 원합니다). 경우에 따라 더 빠를 수도 있습니다.

속도를 높이려는 또 다른 옵션은 동일한 해시 검사 메커니즘을 사용하지만 일치하는 문자열의 줄을 신중하게 "파싱"한 다음 해시와 비교하는 것입니다.

루비에서는 다음과 같이 보일 수 있습니다.

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) 복잡성을 피합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.