Um enorme arquivo de texto (com até 2 GiB) contém cerca de 100 duplicatas exatas de cada linha (inútil no meu caso, pois o arquivo é uma tabela de dados semelhante a CSV).
O que eu preciso é remover todas as repetições enquanto (de preferência, mas isso pode ser sacrificado por um aumento significativo no desempenho), mantendo a ordem da sequência original. No resultado, cada linha deve ser única. Se houvesse 100 linhas iguais (geralmente as duplicatas estão espalhadas pelo arquivo e não serão vizinhas), resta apenas uma do tipo.
Eu escrevi um programa no Scala (considere Java se você não conhece o Scala) para implementar isso. Mas talvez haja ferramentas nativas escritas em C mais rápidas, capazes de fazer isso mais rapidamente?
ATUALIZAÇÃO: a awk '!seen[$0]++' filename
solução parecia estar funcionando bem para mim, desde que os arquivos estivessem perto de 2 GiB ou menores, mas agora como eu estou limpando um arquivo de 8 GiB, ele não funciona mais. Parece levar o infinito em um Mac com 4 GiB de RAM e um PC Windows 7 de 64 bits com 4 GiB de RAM e 6 GiB de swap fica sem memória. E não me sinto entusiasmado em experimentá-lo no Linux com 4 GiB de RAM, dada essa experiência.
sort -u
provavelmente será mais rápido.Respostas:
Uma
awk
solução vista em #bash (Freenode):fonte
awk
versão mais detalhada, usando 2 pesquisas de matriz (mostradas como uma explicação expandida na resposta de Gilles): 0m36.132s vs 0m49.958s .. para 50 milhões de linhas .. pensei que o gargalo seria a E / S, mas a pesquisa de variedade extra é ... 1 milhão de elementos na matriz parece fazer um dente bastante significativo ...Existe um método simples (o que não é óbvio) usando utilitários padrão que não exigem
sort
muita memória, exceto para serem executados , que na maioria das implementações possui otimizações específicas para arquivos grandes (um bom algoritmo de classificação externa). Uma vantagem desse método é que ele apenas percorre todas as linhas dentro de utilitários para fins especiais, nunca dentro de linguagens interpretadas.Se todas as linhas começarem com um caractere que não seja um espaço em branco, você poderá dispensar algumas das opções:
Para uma grande quantidade de duplicação, um método que requer apenas o armazenamento de uma única cópia de cada linha na memória terá um desempenho melhor. Com alguma sobrecarga de interpretação, há um script awk muito conciso para isso (já publicado pelo enzotib ):
Menos concisamente:,
!seen[$0] {print} {seen[$0] += 1}
ou seja, imprima a linha atual, se ainda não foi vista, e aumente oseen
contador dessa linha (variáveis não inicializadas ou elementos de matriz têm o valor numérico 0).Para linhas longas, você pode economizar memória mantendo apenas uma soma de verificação não falsificada (por exemplo, um resumo criptográfico) de cada linha. Por exemplo, usando SHA-1, você só precisa de 20 bytes mais uma sobrecarga constante por linha. Mas a computação digesta é bastante lenta; esse método só vencerá se você tiver uma CPU rápida (especialmente uma com um acelerador de hardware para calcular os resumos) e não houver muita memória em relação ao tamanho do arquivo e linhas suficientemente longas. Nenhum utilitário básico permite calcular uma soma de verificação para cada linha; você teria que suportar a sobrecarga de interpretação do Perl / Python / Ruby /… ou escrever um programa compilado dedicado.
fonte
awk '!seen[$0]++'
, significa que se o awk vir duas linhas duplicadas, ele manterá a primeira sempre e ignorará todas as linhas subseqüentes? (Ou ele irá manter o último?)sort -u
muda a ordem. Minha resposta mostra soluções que preservam a ordem (a ordem das primeiras ocorrências, para ser mais preciso).Observe que o arquivo de saída será classificado.
fonte
awk
comando em outras respostas, mas conceitualmente simples!sort -u
para remover duplicatas durante a classificação, e não depois. (E economiza largura de banda de memória) canalizando-o para outro programa). Isso só é melhor que aawk
versão se você deseja que sua saída seja classificada também. (O OP sobre esta questão quer a sua disposição original preservada , por isso esta é uma boa resposta para um caso de uso um pouco diferente.)Supondo que você possa manter tanto quanto o arquivo desduplicado na memória (se seus dados forem realmente duplicados por um fator de 100, ou seja, cerca de 20MiB +), você poderá fazer isso muito facilmente com o Perl.
Isso preserva a ordem também.
Você pode extrair o número de ocorrências de cada linha do
%dup
hash, se desejar, como um bônus grátis adicional.Se você preferir
awk
, também deve fazê-lo (mesma lógica da versão perl, mesma ordem, mesmos dados reunidos nadup
variável):fonte
uniq
faz isso por si sóComo nenhuma outra resposta forneceu suporte no local, aqui está uma:
fonte
GNU Awk 4.0.2
Você pode usar
uniq
http://www.computerhope.com/unix/uuniq.htmuniq
relata ou filtra linhas repetidas em um arquivo.fonte
'uniq' does not detect repeated lines unless they are adjacent.
Portanto, você deve primeiro classificá-lo e perder a ordem das linhas não duplicadas.Forros do Python One:
fonte
OrderedDict
Nenhuma das respostas aqui funcionou para mim no meu Mac, por isso escrevi um script python simples que funciona para mim. Estou ignorando os espaços em branco iniciais / finais e também não me importo com o consumo de memória.
Salve o acima em unique.py e execute assim:
fonte
Com o bash 4, pode ser usada uma solução pura do bash que aproveita as matrizes associativas . Aqui está um exemplo
fonte
read
loops para processar grandes arquivos de texto. O bash precisa ler um byte de cada vez para evitar ultrapassar uma nova linha. O Bash também não é muito rápido no processamento de texto em geral, comparado ao awk. Se você usar isso,read -ra
vai evitar comer barras invertidas em sua entrada. Além disso, não se esqueça deunset llist
depois do loop, se você colocar isso em uma função shell ou usá-lo interativamente.