Encontre interseção de linhas em dois arquivos [duplicado]

7

Se eu tiver dois arquivos (com colunas únicas), um assim (arquivo1)

34
67
89
92
102
180
blue2
3454

E o segundo arquivo (arquivo2)

23
56
67
69
102
200

Como faço para encontrar elementos que são comuns em ambos os arquivos (interseção)? A saída esperada neste exemplo é

67
102

Observe que o número de itens (linhas) em cada arquivo é diferente. Números e seqüências de caracteres podem ser misturados. Eles podem não ser necessariamente classificados. Cada item aparece apenas uma vez.

ATUALIZAÇÃO:

Verificação do tempo com base em algumas das respostas abaixo.

# generate some data
>shuf -n2000000 -i1-2352452 > file1
>shuf -n2000000 -i1-2352452 > file2

#@ilkkachu
>time (join <(sort "file1") <(sort "file2") > out1)
real    0m15.391s
user    0m14.896s
sys     0m0.205s

>head out1
1
10
100
1000
1000001

#@Hauke
>time (grep -Fxf "file1" "file2" > out2)
real    0m7.652s
user    0m7.131s
sys     0m0.316s

>head out2
1047867
872652
1370463
189072
1807745

#@Roman
>time (comm -12 <(sort "file1") <(sort "file2") > out3)
real    0m13.533s
user    0m13.140s
sys     0m0.195s

>head out3
1
10
100
1000
1000001

#@ilkkachu
>time (awk 'NR==FNR { lines[$0]=1; next } $0 in lines' "file1" "file2" > out4)
real    0m4.587s
user    0m4.262s
sys     0m0.195s

>head out4
1047867
872652
1370463
189072
1807745

#@Cyrus   
>time (sort file1 file2 | uniq -d > out8)
real    0m16.106s
user    0m15.629s
sys     0m0.225s

>head out8
1
10
100
1000
1000001


#@Sundeep
>time (awk 'BEGIN{while( (getline k < "file1")>0 ){a[k]}} $0 in a' file2 > out5)
real    0m4.213s
user    0m3.936s
sys     0m0.179s

>head out5
1047867
872652
1370463
189072
1807745

#@Sundeep
>time (perl -ne 'BEGIN{ $h{$_}=1 while <STDIN> } print if $h{$_}' <file1 file2 > out6)
real    0m3.467s
user    0m3.180s
sys     0m0.175s

>head out6
1047867
872652
1370463
189072
1807745

A versão perl foi a mais rápida seguida por awk. Todos os arquivos de saída tinham o mesmo número de linhas.

Para fins de comparação, classifiquei a saída numericamente para que a saída seja idêntica.

#@ilkkachu
>time (join <(sort "file1") <(sort "file2") | sort -k1n > out1)
real    0m17.953s
user    0m5.306s
sys     0m0.138s

#@Hauke
>time (grep -Fxf "file1" "file2" | sort -k1n > out2)
real    0m12.477s
user    0m11.725s
sys     0m0.419s

#@Roman
>time (comm -12 <(sort "file1") <(sort "file2") | sort -k1n > out3)
real    0m16.273s
user    0m3.572s
sys     0m0.102s

#@ilkkachu
>time (awk 'NR==FNR { lines[$0]=1; next } $0 in lines' "file1" "file2" | sort -k1n > out4)
real    0m8.732s
user    0m8.320s
sys     0m0.261s

#@Cyrus   
>time (sort file1 file2 | uniq -d > out8)
real    0m19.382s
user    0m18.726s
sys     0m0.295s

#@Sundeep
>time (awk 'BEGIN{while( (getline k < "file1")>0 ){a[k]}} $0 in a' file2 | sort -k1n > out5)
real    0m8.758s
user    0m8.315s
sys     0m0.255s

#@Sundeep
>time (perl -ne 'BEGIN{ $h{$_}=1 while <STDIN> } print if $h{$_}' <file1 file2 | sort -k1n > out6)
real    0m7.732s
user    0m7.300s
sys     0m0.310s

>head out1
1
2
3
4
5

Todas as saídas agora são idênticas.

    
por rmf 20.01.2018 / 11:19

5 respostas

5

Em awk , isso carrega o primeiro arquivo totalmente na memória:

$ awk 'NR==FNR { lines[$0]=1; next } $0 in lines' file1 file2 
67
102

Ou, se você quiser acompanhar quantas vezes uma determinada linha aparece:

$ awk 'NR==FNR { lines[$0] += 1; next } lines[$0] {print; lines[$0] -= 1}' file1 file2

join poderia fazer isso, embora exija que os arquivos de entrada sejam classificados, então você precisa fazer isso primeiro, e fazê-lo perde a ordem original:

$ join <(sort file1) <(sort file2)
102
67
    
por 20.01.2018 / 11:30
9

Solução simples comm + sort :

comm -12 <(sort file1) <(sort file2)
  • -12 - suprima a coluna 1 e 2 (linhas exclusivas para FILE1 e FILE2 respectivamente), produzindo apenas linhas comuns (que aparecem em ambos os arquivos)
por 20.01.2018 / 11:34
3

awk

awk 'NR==FNR { p[NR]=$0; next; }
   { for(val in p) if($0==p[val]) { delete p[val]; print; } }' file1 file2

Esta é a boa solução porque (para arquivos grandes) ela deve ser a mais rápida, já que ela não imprime a mesma entrada mais de uma vez e verifica uma entrada novamente depois de ter sido correspondida.

grep

grep -Fxf file1 file2

Isso produziria a mesma entrada várias vezes se ocorrer mais de uma vez em file2 .

classificar

Por diversão (deve ser muito mais lento que grep ):

sort -u file1 >t1
sort -u file2 >t2
sort t1 t2 | uniq -d
    
por 20.01.2018 / 11:28
2

Com o GNU uniq:

sort file1 file2 | uniq -d

Saída:

102
67
    
por 20.01.2018 / 12:18
1

ligeiramente diferente awk versão e equivalente perl versão

tempo relatado para três corridas consecutivas

$ # just realized shuf -n2000000 -i1-2352452 can be used too ;)
$ shuf -i1-2352452 | head -n2000000 > f1
$ shuf -i1-2352452 | head -n2000000 > f2

$ time awk 'NR==FNR{a[$1]; next} $0 in a' f1 f2 > t1
real    0m3.322s
real    0m3.094s
real    0m3.029s

$ time awk 'BEGIN{while( (getline k < "f1")>0 ){a[k]}} $0 in a' f2 > t2
real    0m2.731s
real    0m2.777s
real    0m2.801s

$ time perl -ne 'BEGIN{ $h{$_}=1 while <STDIN> } print if $h{$_}' <f1 f2 > t3
real    0m2.643s
real    0m2.690s
real    0m2.630s

$ diff -s t1 t2
Files t1 and t2 are identical
$ diff -s t1 t3
Files t1 and t3 are identical

$ du -h f1 f2 t1
15M f1
15M f2
13M t1
    
por 20.01.2018 / 14:10