Xóa tất cả các dòng trong tệp A có chứa các chuỗi trong tệp B


15

Tôi có tệp CSV users.csvcó danh sách tên người dùng, tên người dùng và dữ liệu khác:

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"

Trong một tệp khác toremove.txttôi có một danh sách các userID:

30923833
77392318

Có cách nào thông minh, hiệu quả để xóa tất cả các hàng khỏi users.csvtệp có chứa ID toremove.txtkhông? Tôi đã viết một ứng dụng Python đơn giản để phân tích hai tệp và ghi vào một tệp mới chỉ những dòng không được tìm thấy toremove.txt, nhưng nó rất chậm. Có lẽ một số sedhoặc awkphép thuật có thể giúp đỡ ở đây?

Đây là kết quả mong muốn, xem xét các ví dụ trên:

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

Có lẽ bạn nên chia sẻ kịch bản python của bạn. Tôi nghi ngờ có điều gì đó không đúng ở đó, như là O (N²) Mặc dù nếu bạn giữ và xóa hàng triệu hồ sơ thì ma thuật sẽ không giúp được gì nhiều.
Ángel

Kịch bản trong thực tế là O (n <sup> 2 </ sup>): n cho các users.csvdòng của tệp và n cho các dòng của toremove.txt. Tôi không thực sự chắc chắn làm thế nào để làm điều đó với độ phức tạp thấp hơn. Ý chính của nó là : for u in users: if not any(toremove in u): outputfile.write(u). Tôi có thể đăng nó lên Code Review.
dotancohen

1
Tôi sẽ đọc toremove.txt, lưu các mục làm chìa khóa . Lặp lại users.csv, in những nơi mà id không có trong dict. Bạn nhận được xử lý O (n) cho cả toremove.txtvà và users.csvsử dụng bộ nhớ O (n) cho toremove.txt(có lẽ tương đối nhỏ)
Ángel

@ Ángel: Vâng, đó chính xác là cách kịch bản hoạt động!
dotancohen

1
Kiểm tra nếu một khóa tồn tại trong từ điển, bằng với kiểm tra bảng băm, đó là (gần như) O (1). Mặt khác, nếu nó cần lặp lại các mục để loại bỏ, đó là O (m)
Ángel

Câu trả lời:


15

Với grep, bạn có thể làm:

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

Với 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: Đăng! Tôi sẽ nói rằng. Tuy nhiên, xin lưu ý rằng câu trả lời của Gnouc (được cho là) ​​thực hiện những gì câu hỏi yêu cầu , nhưng nó có thể không phải là những gì người dùng muốn.
Scott

Các awkgiải pháp là rất nhạy cảm với các tập tin được định dạng chính xác như trong câu hỏi. Đáng chú ý nhất, nếu một tên chỉ là một từ / mã thông báo (nghĩa là nó không chứa khoảng trắng; ví dụ "Bono":) hoặc nhiều hơn hai mã thông báo (nghĩa là nó chứa nhiều hơn một khoảng trắng, ví dụ "Sir Paul McCartney":), nó sẽ đi qua ngay cả khi kết hợp userid. Ít rõ ràng hơn, điều tương tự xảy ra nếu không có khoảng trắng giữa dấu phẩy đầu tiên và userid hoặc nếu có nhiều hơn một khoảng trắng (ví dụ "John Lennon", 90123412, …:).
Scott

@ Hủy bỏ: Vâng, đó là lý do tôi đặt awkgiải pháp phía saugrep
cuonglm

4

Đây là awkcâu trả lời của Gnouc , được sửa đổi thành mù không gian:

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

Kể từ khi nó chỉ sử dụng dấu phẩy (và không gian) như delimiters, $1"John Lennon", $2 90123412(với một không gian hàng đầu), vv Vì vậy, chúng tôi sử dụng gensubđể loại bỏ bất kỳ số lượng không gian hàng đầu từ $2 trước khi chúng tôi kiểm tra xem nó (userid) là trong toremove.txttập tin.


Bạn có thể thực hiện một số công cụ thông minh khác ở đây (chỉ cần suy nghĩ thành tiếng) như phân tích "phần chính xác" của chuỗi không khớp và so sánh với mảng kết hợp hoặc không.
rogerdpack

Tôi tin rằng đó là những gì tôi đang làm. Bạn đã nghĩ gì vậy?
Scott

Vâng, đúng vậy Tôi chỉ đề cập đến nếu bạn cần phải làm điều gì đó sôi nổi hơn như loại bỏ nửa đầu của một dòng hay bất cứ điều gì như thế (downcasing vv stackoverflow.com/a/4784647/32453 ) chỉ chuyên phân tích cú pháp
rogerdpack

0

OK một cách ruby: nếu bạn có một danh sách các chuỗi trong một tệp và bạn muốn xóa tất cả các dòng khỏi một tệp khác thậm chí chứa bất kỳ chuỗi nào trong tệp đầu tiên (trong trường hợp này là xóa "file2" khỏi tệp ruby ​​"file1") :

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

Thật không may với một tệp "loại bỏ" lớn, điều này dường như làm giảm độ phức tạp thành O (N ^ 2) (giả định của tôi là regrec có rất nhiều việc phải làm), nhưng vẫn có thể hữu ích cho ai đó ngoài đó (nếu bạn muốn nhiều hơn là loại bỏ các dòng đầy đủ). Nó có thể nhanh hơn trong một số trường hợp.

Một tùy chọn khác nếu bạn muốn tăng tốc là sử dụng cùng một cơ chế kiểm tra băm, nhưng cẩn thận "phân tích" dòng cho các chuỗi có thể khớp, sau đó so sánh chúng với hàm băm của bạn.

Trong ruby, có thể trông như thế này:

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

Xem thêm câu trả lời của Scott, nó tương tự như câu trả lời awk được đề xuất ở đây và tránh sự phức tạp của O (N ^ 2) (phew).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.