Видаліть усі рядки у файлі A, які містять рядки у файлі B


15

У мене є файл 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мене є список ідентифікаторів користувача:

30923833
77392318

Чи є розумний, ефективний спосіб видалити всі рядки з users.csvфайлу, в якому містяться ідентифікатори toremove.txt? Я написав простий додаток Python, щоб проаналізувати два файли і записати в новий файл лише ті рядки, в яких не знайдено toremove.txt, але це надзвичайно повільно. Можливо , деякі sedабо awkмагія може допомогти тут?

Це бажаний результат, враховуючи наведені вище приклади:

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

Можливо, вам слід поділитися сценарієм python. Я підозрюю, що там щось не так, як, наприклад, O (N²). Хоча, якщо ви зберігаєте і виймаєте мільйони записів, магія не дуже допоможе.
Ángel

Сценарій насправді O (n <sup> 2 </sup>): n для users.csvрядків файлу, і n для рядків toremove.txt. Я не дуже впевнений, як це зробити з меншою складністю. Суть його полягає в : for u in users: if not any(toremove in u): outputfile.write(u). Я можу опублікувати його в Code Review.
dotancohen

1
Я б читав toremove.txt, зберігаючи записи як ключі . Iterate users.csv, друкуючи ті, де ідентифікатор не вказаний. Ви отримуєте обробку O (n) і для toremove.txtі users.csv, і для O (n) використання пам'яті toremove.txt(що, мабуть, порівняно мало)
Á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 (можливо) відповідає тому, що запитує , але це може бути не тим, чого хоче користувач.
Скотт

awkРішення дуже чутливо до файлів бути відформатовані точно , як показано в цьому питанні. Найяскравіше, якщо ім'я - це лише одне слово / лексема (тобто воно не містить пробілів; наприклад, "Bono") або більше двох лексем (тобто воно містить більше одного пробілу; наприклад, "Sir Paul McCartney"), воно пройде, навіть якщо userid матчі. Менш очевидно, те ж саме відбувається, якщо між першою комою та користувачем немає пробілу, або якщо є більше простору (наприклад, "John Lennon", 90123412, …).
Скотт

@Scott: Так, саме тому я поставив awkрішенняgrep
cuonglm

4

Ось відповідь 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 перш ніж перевірити, чи був він (userid) у toremove.txtфайлі.


Тут ви можете зробити якісь інші розумні речі (просто роздумуючи вголос), як розбирати "точний шматок" рядка, який не повинен відповідати, і порівнювати це з асоціативним масивом, або що ні.
rogerdpack

Я вважаю, що це я роблю. Що ви мали на увазі?
Скотт

Так і є. Я просто в увазі , якщо вам потрібно зробити що - то фанковиє як видалення першу половину лінії або що - небудь подібне (downcasing і т.д. stackoverflow.com/a/4784647/32453 ) тільки спеціалізований синтаксичний аналіз
rogerdpack

0

OK OK ruby ​​way: якщо у вас є список рядків у файлі, і ви хочете видалити всі рядки з іншого файлу, які навіть містять будь-який рядок у першому файлі (у цьому випадку видалення "file2" з "file1") 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) (моє припущення, що регулярний вигляд має багато роботи), але все-таки може бути корисним для когось там (якщо ви хочете більше, ніж видаляти повні рядки). У певних випадках це може бути швидше.

Інший варіант, якщо ви збираєтесь на швидкість, - це використовувати той же механізм перевірки хешу, але ретельно "розбирати" рядок для рядків, які можуть відповідати, а потім порівнювати їх з вашим хешем.

У рубіні це може виглядати так:

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) (Pеw).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.