Supprimer les champs en double dans une colonne donnée

Je voudrais supprimer d'une colonne donnée ($ 2 dans l'exemple) les champs en double (séparés par des virgules).

Fichier d'input:

A 1,2,3,4 B 4,5,6,3 C 2,15 

Production attendue:

 A 1,2,3,4 B 5,6 C 15 

 perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e' 

Vous pouvez exécuter le ci-dessus comme ceci:

 $ perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e' afile A 1,2,3,4 B 5,6 C 15 

Comment ça marche

Premier appel perl avec -lpe fait les 3 choses suivantes.

  • -l[octal] active le traitement de fin de ligne, spécifie la terminaison de ligne
  • -p assume la boucle comme -n mais ligne d'printing aussi, comme sed
  • -e program une ligne de programme (plusieurs -e est autorisé, omet le file programme)

Ceci prend essentiellement le file, supprime les returns, opère sur une ligne, puis returnne un caractère newline quand c'est fait. Donc, il suffit de parcourir le file et d'exécuter notre code Perl à tour de rôle.

En ce qui concerne le code Perl actuel:

  • \s signifie un caractère d'espacement (les cinq caractères [ \f\n\r\t] et \v dans les versions plus récentes de perl , comme [[:space:]] ).
  • \K Gardez les choses à gauche du \ K, ne l'incluez pas dans $ &
  • \S+ un ou plusieurs caractères qui ne sont pas dans l'set [\ f \ n \ r \ t \ v]

La join ",", va prendre les résultats et join chaque champ afin qu'il soit séparé par une virgule.

Le split ",", $& prendra les correspondances qui ont été trouvées par le \S+ et les divise en seulement les champs, sans la virgule.

Le grep {!$seen{$_}++} prendra le numéro de chaque champ, l'appenda au hachage, $seen{} où le numéro de chaque champ est $_ fur et à mesure de chacun d'entre eux. Chaque fois qu'un numéro de champ est "vu", il est compté via l'opérateur ++ , $seen{$_}++ .

Le grep{!$seen{$_}++} renvoie une valeur de champ si elle n'a été vue qu'une seule fois.

Modifié pour voir ce qui se passe

Si vous utilisez cette abomination modifiée, vous pouvez voir ce qui se passe alors que cette ligne Perl se déplace sur les lignes du file.

 $ perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e; @a=keys %seen; @b=values %seen; print "keys: @a | vals: @b"' afile keys: 4 1 3 2 | vals: 1 1 1 1 A 1,2,3,4 keys: 6 4 1 3 2 5 | vals: 1 2 1 2 1 1 B 5,6 keys: 6 4 1 3 2 15 5 | vals: 1 2 1 2 2 1 1 C 15 

Cela vous montre le contenu de $seen{} à la fin du traitement d'une ligne du file. Prenons la deuxième ligne du file.

 B 4,5,6,3 

Et voici ce que ma version modifiée montre cette ligne comme:

 keys: 6 4 1 3 2 15 5 | vals: 1 2 1 2 2 1 1 

Donc, nous disons que nous avons vu le champ # 6 (1 fois), le champ # 4 (2 fois), etc. et le champ # 5 (1 fois). Ainsi, lorsque grep{...} renvoie les résultats, il renvoie uniquement les résultats de ce tableau s'il était présent dans cette ligne (4,5,6,3) et si nous l'avons vu seulement 1 fois (6,1, 15,5). L'intersection de ces 2 lists est (5,6) et c'est ce qui est returnné par grep .

Les references

  • perlre – perldoc.perl.org
 { tr -cs '0-9ABC' '[\n*]' | nl -w1 -s: | sort -t: -uk2,2 | sort -t: -k1,1n | sed 's/[^:]*://;/^[ABC]/!{H;$!d };x;y/\n/,/;s/,/\t/' } <<\FILE A 1,2,3,4 B 4,5,6,3 C 2,15 FILE 

Fondamentalement, cela prend juste quelques mesures pour s'assurer que datatables peuvent être exécutées par le biais de sort -u et reviennent toujours dans la même forme qu'il est sorti.

Tout d'abord tr transforme tout octet dans son input qui n'est pas un chiffre ou ABC en une nouvelle ligne et serre toutes les répétitions. Chaque champ obtient sa propre ligne.

nl numérote ensuite chaque ligne dans son input. Sa sortie ressemble à:

 1:A 2:1 3:2 

…etc.

Ensuite, datatables sont sort – deux fois. Le premier sort supprime tous les champs dupliqués – il fonctionne uniquement sur son deuxième champ de données par ligne et sépare ces champs avec un : Le second sort restaure l'ordre original – l'ordre numéroté de nl – en sortingant le premier champ cette fois.

Last sed remet tout en place. Il collecte ses inputs, supprime toutes les insertions de nl et e x change ses tampons de maintien et de motif pour les lignes ABC et les $ derniers. Sur chacun de ceux-ci, il transforme tous les ewlines en , virgules et derniers substituts d'un <tab> pour le premier de ceux-ci par ligne.

Et les résultats?

SORTIE

 A 1,2,3,4 B 5,6 C 15 

Et ça, les amis, c'est ce qui rend Unix génial.

Et, parce que j'étais curieux, ici c'est avec sed seul:

 sed 's/\t\(.*\)/,\1,/;H;$!d;x;:t s/,\([^,]*,\)\(.*,\)\1/,\1\2/;tt s/,\(\n.\),/\1\t/g;s/,\(.*\),/\t\1/' ' <<\FILE A 1,2,3,4 B 4,5,6,3 C 2,15 FILE 

Les première et troisième lignes sont comme, prep / cleanup. Le premier extrait le file entier dans l'espace des motifs et garantit que chaque champ est délimité par des virgules, de sorte que la première ligne, par exemple:

 A,1,2,3,4, 

ressemble à ça après qu'il soit fini.

La deuxième ligne est où tout le travail est fait. Il s'agit en fait d'une fonction de rlocation récursive – elle remplace continuellement tout champ délimité par des virgules qui peut être associé ailleurs avec lui-même jusqu'à ce qu'il n'y en ait plus. C'est la fonction de la command t est.

La troisième ligne, comme je l'ai dit, nettoie tout. Il remet les virgules là où elles appartiennent et les tabs et etc, et le résultat final est:

SORTIE

 A 1,2,3,4 B 5,6 C 15 

C'était amusant, mais faire cela avec n'importe quel nombre important de données est un meurtre informatique. C'est une regex récursive – la pire sorte de récursivité, probablement. C'est donc une bonne chose à faire avec seulement quelques lignes, mais faire une feuille de calcul de cette façon est probablement mal conseillé.

Avec awk , cela est assez facile en utilisant un array , une split et une loop régulière:

 { split($2, elements, ",") out = "" for (i in elements) { el = elements[i] if (!(el in used)) { out = out el "," } used[el] = 1 } sub(/,$/, "", out) $2 = out } 1 

Pour chaque ligne, nous divisons la deuxième colonne par des virgules et sauvegardons les bits dans un tableau. Nous construisons ensuite la nouvelle valeur pour cette colonne avec une boucle, vérifiant si nous avons vu la valeur avant ou non. Nous conservons l'set de valeurs que nous avons déjà vu dans le tableau (associatif) used . Si el in used , nous avons vu celui-ci avant et ne devrait pas le mettre dans la sortie; sinon, c'est nouveau, et nous le concaténons et l'ajoutons à notre set de valeurs vues afin que nous ne l'utilisions plus. Enfin, nous remettons la list construite dans la deuxième colonne. C'est essentiellement l'approche que vous adopteriez dans n'importe quelle autre langue.

Placez le code ci-dessus dans un file et exécutez-le avec awk -f , ou indiquez-le comme un argument sur la command line.