Удалить все строки в файле A, которые содержат строки в файле B

15

У меня есть CSV-файл users.csvсо списком userNames, userID и другими данными:

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"
dotancohen
источник
Может быть, вы должны поделиться своим скриптом Python. Я подозреваю, что там что-то не так, например, быть O (N²). Хотя, если вы храните и удаляете миллионы записей, магия не поможет слишком сильно.
Анхель
Сценарий на самом деле 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, сохранив записи как ключи . Итерируйте users.csv, печатая те, в которых идентификатор не указан. Вы получаете O (n) для обработки как toremove.txtи users.csv, так и O (n) для использования памяти toremove.txt(что, вероятно, относительно мало)
Анхель
@ Анхель: Да, именно так работает сценарий!
dotancohen
1
Проверка наличия ключа в словаре соответствует проверке хеш-таблицы, которая (почти) равна O (1). С другой стороны, если нужно выполнить итерации для удаления элементов, это O (м)
Анхель

Ответы:

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"
cuonglm
источник
@terdon: Черт! Я собирался сказать это. Обратите внимание, однако, что ответ Gnouc (возможно) делает то, что просит вопрос , но это может быть не то, что хочет пользователь.
Скотт
awkРешение очень чувствительно к файлам быть отформатированы точно , как показано в этом вопросе. Наиболее очевидно, что если имя представляет собой всего одно слово / токен (т. Е. Оно не содержит пробелов; например, "Bono") или содержит более двух токенов (т. Е. Оно содержит более одного пробела; например, "Sir Paul McCartney"), оно будет проходить, даже если совпадения идентификаторов пользователей. Менее очевидно, то же самое происходит, если между первой запятой и идентификатором пользователя нет пробела или если имеется несколько пробелов (например, "John Lennon", 90123412, …).
Скотт
@ Скотт: Да, это причина, по которой я оставил awkрешениеgrep
cuonglm
4

Вот awkответ Gnouc , измененный, чтобы быть невидимым :

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
Я верю, что это то, что я делаю. Что у тебя было на уме?
Скотт
Да, вы. Я просто в виду , если вам нужно сделать что - то фанковые как удаление первую половину линии или что - нибудь подобное (downcasing и т.д. stackoverflow.com/a/4784647/32453 ) только специализированный синтаксический анализ
rogerdpack
0

ОК, путь ruby: если у вас есть список строк в файле, и вы хотите удалить все строки из другого файла, которые даже содержат любую строку в первом файле (в этом случае удаление «file2» из «file1») файла ruby :

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) (фу).

rogerdpack
источник