web-dev-qa-db-fra.com

Trouvez l'intersection des lignes dans deux fichiers

Si j'ai deux fichiers (avec des colonnes simples), une personne comme (fichier1)

34
67
89
92
102
180
blue2
3454

Et le deuxième fichier (fichier2)

23
56
67
69
102
200

Comment trouver des éléments communs dans les deux fichiers (intersection)? La production attendue dans cet exemple est

67
102

Notez que le nombre d'éléments (lignes) dans chaque fichier diffère. Les chiffres et les chaînes peuvent être mélangés. Ils peuvent ne pas être forcément triés. Chaque élément apparaît seulement une fois.

METTRE À JOUR:

Chèque de temps Basé sur certaines des réponses ci-dessous.

# 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

La version Perl était la plus rapide suivie de AWK. Tous les fichiers de sortie ont eu le même nombre de lignes.

Pour des raisons de comparaison, j'ai trié la sortie numériquement de manière à ce que la sortie soit identique.

#@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

Toutes les sorties sont maintenant identiques.

13
rmf

Dans awk, ceci charge le premier fichier entièrement en mémoire:

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

Ou, si vous voulez garder une trace de combien de fois une ligne donnée apparaît:

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

join pourrait faire cela, bien qu'il exige que les fichiers d'entrée soient triés, vous devez donc le faire en premier et le faire perd la commande originale:

$ join <(sort file1) <(sort file2)
102
67
8
ilkkachu

Simple comm + sort Solution:

comm -12 <(sort file1) <(sort file2)
  • -12 - Supprimer la colonne 1 et 2 (lignes propres à FILE1 et FILE2 respectivement), émettant ainsi uniquement des lignes communes (qui apparaissent dans les deux fichiers)
12
RomanPerekhrest

awk

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

C'est la bonne solution car (pour les fichiers volumineux), il devrait être le plus rapide car il omet à la fois imprimer la même entrée plus d'une fois et vérifier une entrée après sa correspondance.

grep

grep -Fxf file1 file2

Cela émettrait la même entrée à plusieurs reprises s'il se produit plus d'une fois dans file2.

trier

Pour le plaisir (devrait être beaucoup plus lent que grep):

sort -u file1 >t1
sort -u file2 >t2
sort t1 t2 | uniq -d
3
Hauke Laging

Avec GNU UNIQ:

sort file1 file2 | uniq -d

Sortir:

 102 [.____] 67 [.____]
2
Cyrus

légèrement différent awk version et équivalent Perl version

temps rapporté pour trois courses consécutives

$ # 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
1
Sundeep