Encontre IDs em um arquivo que não estão em outro

9

Eu tenho dois arquivos:

abc.txt

abcd
xyz
pqrs

mno.txt

zzon
mkno
abcd
  • Eu quero verificar se "abcd" está lá no arquivo mno.txt .
  • Não é necessário que, se "abcd" for o primeiro em abc.txt , também será o primeiro em mno.txt .
  • Existem milhares de ids em ambos os arquivos.
  • Também quero verificar quantos IDs não estão em mno.txt que estão em abc.txt .

Como posso fazer isso?

    
por Avani badheka 26.06.2017 / 14:32

4 respostas

19

Se o seu objetivo é encontrar linhas comuns ou incomuns, comm seria meu comando aqui.

Compara dois arquivos e mostra - em três colunas - linhas exclusivas do arquivo 1, linhas exclusivas do arquivo 2 e linhas que aparecem nos dois arquivos, respectivamente. Você pode passar os sinalizadores para suprimir qualquer uma dessas saídas também. Por exemplo, comm -1 file1 file2 suprimirá a primeira coluna, as coisas exclusivas do arquivo1. comm -12 file1 file2 mostraria apenas as coisas nos dois arquivos.

Há uma grande advertência: a entrada deve ser classificada. Podemos contornar isso.

Isso mostrará tudo no abc que não está no mno:

comm -23 <(sort abc.txt) <(sort mno.txt)

E você pode canalizar isso em wc -l para obter uma contagem.

O motivo pelo qual eu uso o comm é que, uma vez que os arquivos são classificados, a comparação lado-a-lado é computacionalmente simples. Se você está lidando com milhões deles, isso fará diferença.

Isso pode ser demonstrado com alguns arquivos simulados. Eu tenho um computador bastante rápido para mostrar a diferença entre as abordagens, eu preciso de um conjunto de amostra muito grande. Eu fui para 10 milhões de 10 caracteres por arquivo.

$ cat /dev/urandom | tr -dc '0-9' | fold -w 10 | head -10000000 > abc.txt
$ cat /dev/urandom | tr -dc '0-9' | fold -w 10 | head -10000000 > mno.txt

$ time comm -23 <(sort abc.txt) <(sort mno.txt) | wc -l
... 0m10.653s

$ time grep -Fcxv -f abc.txt mno.txt
... 0m23.920s

$ time grep -Fcwv -f abc.txt mno.txt
... 0m40.313s

$ time awk 'NR==FNR{a[$0]++};NR!=FNR && a[$0]' abc.txt  mno.txt | wc -l
... 0m12.161s

A classificação é o que mais demora na minha. Se fingirmos que o abc.txt é estático, podemos pré-classificá-lo e isso torna futuras comparações muito mais rápidas:

$ sort abc.txt abc-sorted.txt
$ time comm -23 abc-sorted.txt <(sort mno.txt) | wc -l
... 0m7.426s

Você pode considerar esses e considerar alguns segundos irrelevantes, mas devo destacar que eles estão sendo executados em uma máquina de ponta. Se você quisesse fazer isso em um (por exemplo) Raspberry Pi 3, você estará olhando para reviravoltas muito mais lentas e a diferença aumentará a um ponto que realmente importa.

    
por Oli 26.06.2017 / 14:51
7

para obter uma lista:

grep -Fwf abc.txt mno.txt

dá-lhe algo semelhante a:

abcd
abcd
zef

Se você quiser apenas obter uma lista única, use-a como:

grep -Fwf abc.txt mno.txt | sort | uniq

e para obter as contagens:

grep -Fcwv -f abc.txt mno.txt
  • -F significa: interprete PATTERN como uma lista de sequências fixas em vez de expressões regulares.
  • -f obtém padrões do FILE que serão abc.txt .
  • analisamos mno.txt para padrões
  • -c Contar o número de correspondências
  • -w Apenas procura por "palavras inteiras": a substring correspondente deve estar no início da linha ou precedida por um caractere não constituinte do Word. Da mesma forma, deve ser no final da linha ou seguido por um caractere constituinte não-palavra. Os caracteres constituintes da palavra são letras, dígitos e sublinhado.
  • -v Reverter a pesquisa
por Ravexina 26.06.2017 / 14:46
3

Poderíamos usar o awk para fazer o trabalho passando dois arquivos, primeiro o arquivo padrão, depois o arquivo que queremos verificar. Quando estamos lendo o primeiro arquivo, sabemos que NR==FNR e, nesse momento, podemos ler linhas em array. Quando NR!=FNR , verificamos se a matriz dessa linha está definida.

$ cat abc.txt                                                      
abcd
xyz
pqrs
$ cat mno.txt                                                      
zzon
xyz
mkno
abcd
$ awk 'NR==FNR{a[$0]++};NR!=FNR && a[$0]' abc.txt  mno.txt         
xyz
abcd

Por outro lado, podemos negar o padrão para imprimir as linhas que não estão em abc.txt

$ awk 'NR==FNR{a[$0]++};NR!=FNR && ! a[$0]' abc.txt  mno.txt       
zzon
mkno

E se quisermos imprimir a contagem, podemos empregar sort e wc :

$ awk 'NR==FNR{a[$0]++};NR!=FNR && ! a[$0]' abc.txt  mno.txt | sort -u | wc -l         
2
    
por Sergiy Kolodyazhnyy 26.06.2017 / 18:12
2

Se uma das listas de palavras não estiver classificada, seria mais rápido usar uma estrutura de dados eficiente para lembrar as palavras comuns.

Python

#!/usr/bin/env python3
import sys

with open(sys.argv[1]) as minuend_file:
    minuend = frozenset(map(str.rstrip, minuend_file))
with open(sys.argv[2]) as subtrahend_file:
    subtrahend = frozenset(map(str.rstrip, subtrahend_file))

difference = minuend - subtrahend
#print(*difference, sep='\n') # This prints the content of the set difference
print(len(difference)) # This prints the magnitude of the set difference

Uso:

python3 set-difference.py abc.txt mno.txt

Python (mais eficiente)

Se você quiser economizar um pouco de memória para armazenamento intermediário e tempo de execução, você pode usar este programa um pouco mais difícil de entender:

#!/usr/bin/env python3
import sys

with open(sys.argv[1]) as minuend_file:
    minuend = set(map(str.rstrip, minuend_file))
with open(sys.argv[2]) as subtrahend_file:
    subtrahend = map(str.rstrip, subtrahend_file)
    minuend.difference_update(subtrahend)
    difference = minuend
    del minuend

#print(*difference, sep='\n') # This prints the content of the set difference
print(len(difference)) # This prints the magnitude of the set difference

Desempenho

Dados abc.txt e mno.txt com 1 milhão de linhas não ordenadas de 10 caracteres aleatórios de dígitos ASCII cada (veja a resposta de Oli para o set-up):

$ time python3 set-difference.py abc.txt mno.txt
user    0m10.453s

vs.

$ export LC_COLLATE=C
$ time sort abc.txt > abc_sorted.txt
user    0m10.652s
$ time sort mno.txt > mno_sorted.txt
user    0m10.767s
$ time comm -23 abc_sorted.txt mno_sorted.txt | wc -l
9989882
user    0m1.600s

total: 23 segundos

    
por David Foerster 27.06.2017 / 15:37