Remova todas as linhas no arquivo A que contêm as seqüências de caracteres no arquivo B


15

Eu tenho um arquivo CSV users.csvcom uma lista de userNames, userIDs e outros dados:

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"

Em outro arquivo toremove.txt, tenho uma lista de userIDs:

30923833
77392318

Existe uma maneira inteligente e eficiente de remover todas as linhas do users.csvarquivo que contém os IDs toremove.txt? Eu escrevi um aplicativo Python simples para analisar os dois arquivos e gravar em um novo arquivo apenas as linhas que não foram encontradas toremove.txt, mas é extraordinariamente lento. Talvez alguma sedou awkmágica possa ajudar aqui?

Este é o resultado desejado, considerando os exemplos acima:

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

Talvez você deva compartilhar seu script python. Suspeito que haja algo errado lá, como ser O (N²) Embora se você estiver mantendo e removendo milhões de registros, a magia não ajudará muito.
Ángel

O script é de fato O (n <sup> 2 </sup>): n para as users.csvlinhas do arquivo en para as linhas de toremove.txt. Não tenho muita certeza de como fazê-lo com menor complexidade. A essência do que é: for u in users: if not any(toremove in u): outputfile.write(u). Posso publicá-lo na Revisão de Código.
dotancohen

1
Eu lia toremove.txt, salvando as entradas como chaves . Iterate users.csv, imprimindo aqueles em que o ID não está no dict. Você começa O (n) o processamento para ambos toremove.txte users.csv, e uso de O (n) de memória para toremove.txt(o que provavelmente é relativamente pequeno)
Ángel

@ Ángel: Sim, é exatamente assim que o script funciona!
dotancohen

1
Verificar se existe uma chave em um dicionário, é igual a uma verificação de tabela de hash, que é (quase) O (1). Por outro lado, se precisar iterar os itens a serem removidos, isso é O (m)
Ángel

Respostas:


15

Com grep, você pode fazer:

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

Com 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: Dang! Eu ia dizer isso. Note, no entanto, que a resposta do Gnouc (sem dúvida) faz o que a pergunta pede , mas pode não ser o que o usuário deseja.
Scott

A awksolução é altamente sensível à formatação dos arquivos exatamente como mostrado na pergunta. Surpreendentemente, se um nome é apenas uma palavra / token (ou seja, não contém espaços; por exemplo "Bono") ou possui mais de dois tokens (ou seja, contém mais de um espaço; por exemplo "Sir Paul McCartney"), será exibido mesmo se o ID do usuário corresponde. Menos obviamente, o mesmo acontece se não houver espaço entre a primeira vírgula e o ID do usuário ou se houver mais de um espaço (por exemplo, "John Lennon", 90123412, …).
21714 Scott

@ Scott: Sim, é a razão de eu colocar awksolução para trásgrep
cuonglm

4

Aqui está a awkresposta do Gnouc , modificada para ser cega no espaço:

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

Como ele usa apenas vírgulas (e não espaços) como delimitadores, $1é "John Lennon", $2é  90123412(com um espaço à esquerda), etc. Portanto, usamos gensubpara remover qualquer número de espaços à esquerda $2 antes de verificar se ele (o ID do usuário) estava no toremove.txtarquivo.


Você pode fazer outras coisas inteligentes aqui (apenas pensando em voz alta) como analisar a "peça exata" da string que não deve corresponder e compará-la com a matriz associativa, ou não.
Rogerdpack #

Eu acredito que é isso que estou fazendo. O que voce tinha em mente?
Scott

Sim você é. Eu estava apenas se referindo a se for necessário para fazer algo mais descolados como remover a primeira metade de uma linha ou qualquer coisa assim (downcasing, etc. stackoverflow.com/a/4784647/32453 ) apenas especializada parsing
rogerdpack

0

OK de uma maneira ruby: se você possui uma lista de strings em um arquivo e deseja remover todas as linhas de outro arquivo que contenham qualquer string no primeiro arquivo (neste caso, removendo "file2" de "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

infelizmente, com um grande arquivo "para remover", isso parece degradar a complexidade para O (N ^ 2) (suponho que o regexp tenha muito trabalho a fazer), mas ainda pode ser útil para alguém por aí (se você quer mais do que remover linhas completas). Pode ser mais rápido em certos casos.

Outra opção, se você estiver buscando velocidade, é usar o mesmo mecanismo de verificação de hash, mas "analisar" cuidadosamente a linha de cadeias que possam corresponder e compará-las com seu hash.

Em ruby, pode ser assim:

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

Veja também a resposta de Scott, que é semelhante às respostas do awk propostas aqui e evita a complexidade de O (N ^ 2) (ufa).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.