Comment supprimer les éléments en double d'un tableau en Perl?

156

J'ai un tableau en Perl:

my @my_array = ("one","two","three","two","three");

Comment supprimer les doublons du tableau?

David
la source

Réponses:

168

Vous pouvez faire quelque chose comme ceci comme démontré dans perlfaq4 :

sub uniq {
    my %seen;
    grep !$seen{$_}++, @_;
}

my @array = qw(one two three two three);
my @filtered = uniq(@array);

print "@filtered\n";

Les sorties:

one two three

Si vous souhaitez utiliser un module, essayez la uniqfonction deList::MoreUtils

Greg Hewgill
la source
28
veuillez ne pas utiliser $ a ou $ b dans les exemples car ce sont les globaux magiques de sort ()
szabgab
2
C'est un mylexical dans ce domaine, donc ça va. Cela étant dit, un nom de variable plus descriptif pourrait être choisi.
éphémère
2
@ephemient oui, mais si vous deviez ajouter un tri dans cette fonction, il l'emporterait $::aet $::b, n'est-ce pas?
vol7ron
5
@BrianVandenberg Bienvenue dans le monde de 1987 - quand il a été créé - et presque 100% de compatibilité de backword pour perl - donc il ne peut pas être éliminé.
szabgab
18
sub uniq { my %seen; grep !$seen{$_}++, @_ }est une meilleure implémentation car elle préserve l'ordre sans frais. Ou mieux encore, utilisez celui de List :: MoreUtils.
ikegami
120

La documentation Perl est fournie avec une belle collection de FAQ. Votre question est fréquemment posée:

% perldoc -q duplicate

La réponse, copiée et collée à partir de la sortie de la commande ci-dessus, apparaît ci-dessous:

Trouvé dans /usr/local/lib/perl5/5.10.0/pods/perlfaq4.pod
 Comment puis-je supprimer les éléments en double d'une liste ou d'un tableau?
   (contribution de brian d foy)

   Utilisez un hachage. Lorsque vous pensez que les mots "unique" ou "dupliqué", pensez
   "clés de hachage".

   Si vous ne vous souciez pas de l'ordre des éléments, vous pouvez simplement
   créez le hachage puis extrayez les clés. Ce n'est pas important de savoir comment vous
   créez ce hachage: utilisez simplement des "clés" pour obtenir les éléments uniques.

       mon% hash = map {$ _, 1} @array;
       # ou une tranche de hachage: @hash {@array} = ();
       # ou un foreach: $ hash {$ _} = 1 foreach (@array);

       mon @unique = clés% hash;

   Si vous souhaitez utiliser un module, essayez la fonction "uniq" de
   "Liste :: MoreUtils". Dans le contexte de la liste, il renvoie les éléments uniques,
   en préservant leur ordre dans la liste. Dans un contexte scalaire, il renvoie le
   nombre d'éléments uniques.

       utilisez List :: MoreUtils qw (uniq);

       mon @unique = uniq (1, 2, 3, 4, 4, 5, 6, 5, 7); # 1,2,3,4,5,6,7
       mon $ unique = uniq (1, 2, 3, 4, 4, 5, 6, 5, 7); # 7

   Vous pouvez également parcourir chaque élément et ignorer ceux que vous avez vus
   avant. Utilisez un hachage pour garder une trace. La première fois que la boucle voit un
   élément, cet élément n'a pas de clé dans% Seen. L'instruction "next" crée
   la clé et utilise immédiatement sa valeur, qui est "undef", donc la boucle
   continue à "pousser" et incrémente la valeur de cette clé. Le suivant
   au moment où la boucle voit ce même élément, sa clé existe dans le hachage et
   la valeur de cette clé est true (car elle n'est pas 0 ou "undef"), donc le
   next ignore cette itération et la boucle passe à l'élément suivant.

       mon @unique = ();
       mon% vu = ();

       foreach mon $ elem (@array)
       {
         suivant si $ vu {$ elem} ++;
         push @unique, $ elem;
       }

   Vous pouvez écrire ceci plus brièvement en utilisant un grep, qui fait la même chose
   chose.

       mon% vu = ();
       mon @unique = grep {! $ vu {$ _} ++} @array;
John Siracusa
la source
17
John iz dans mah anzers voler mah rep!
brian d foy
5
Je pense que vous devriez obtenir des points bonus pour avoir réellement cherché la question.
Brad Gilbert
2
J'aime que la meilleure réponse soit 95% de copier-coller et 3 phrases d'OC. Pour être parfaitement clair, c'est la meilleure réponse; Je trouve juste ce fait amusant.
Parthe Shot
70

Liste d' installation :: MoreUtils du CPAN

Puis dans votre code:

use strict;
use warnings;
use List::MoreUtils qw(uniq);

my @dup_list = qw(1 1 1 2 3 4 4);

my @uniq_list = uniq(@dup_list);
Ranguard
la source
4
Le fait que List :: MoreUtils ne soit pas fourni avec perl endommage un peu la portabilité des projets qui l'utilisent :( (pour ma part, je ne le ferai pas)
yPhil
3
@Ranguard: @dup_listdevrait être à l'intérieur de l' uniqappel, pas@dups
incutonez
@yassinphilip CPAN est l'une des choses qui rendent Perl aussi puissant et génial que possible. Si vous écrivez vos projets basés uniquement sur des modules de base, vous limitez énormément votre code, ainsi que du code éventuellement écrit qui tente de faire beaucoup mieux ce que certains modules font juste pour éviter de les utiliser. De plus, l'utilisation de modules de base ne garantit rien, car différentes versions de Perl peuvent ajouter ou supprimer des modules de base de la distribution, donc la portabilité dépend toujours de cela.
Francisco Zarabozo
24

Ma façon habituelle de faire ceci est:

my %unique = ();
foreach my $item (@myarray)
{
    $unique{$item} ++;
}
my @myuniquearray = keys %unique;

Si vous utilisez un hachage et ajoutez les éléments au hachage. Vous avez également l'avantage de savoir combien de fois chaque élément apparaît dans la liste.

Xetius
la source
2
Cela présente l'inconvénient de ne pas conserver la commande d'origine, si vous en avez besoin.
Nathan Fellman
Il est préférable d'utiliser des tranches au lieu de la foreachboucle:@unique{@myarray}=()
Onlyjob
8

La variable @array est la liste des éléments en double

%seen=();
@unique = grep { ! $seen{$_} ++ } @array;
Sreedhar
la source
7

Peut être fait avec une simple doublure Perl one.

my @in=qw(1 3 4  6 2 4  3 2 6  3 2 3 4 4 3 2 5 5 32 3); #Sample data 
my @out=keys %{{ map{$_=>1}@in}}; # Perform PFM
print join ' ', sort{$a<=>$b} @out;# Print data back out sorted and in order.

Le bloc PFM fait ceci:

Les données dans @in sont introduites dans MAP. MAP crée un hachage anonyme. Les clés sont extraites du hachage et alimentées dans @out

faucon
la source
4

Ce dernier était plutôt bon. Je le peaufinerais juste un peu:

my @arr;
my @uniqarr;

foreach my $var ( @arr ){
  if ( ! grep( /$var/, @uniqarr ) ){
     push( @uniqarr, $var );
  }
}

Je pense que c'est probablement la manière la plus lisible de le faire.

jh314
la source
4

Méthode 1: utiliser un hachage

Logique: un hachage ne peut avoir que des clés uniques, donc itérer sur un tableau, attribuer une valeur à chaque élément du tableau, en gardant l'élément comme clé de ce hachage. Retournez les clés du hachage, c'est votre tableau unique.

my @unique = keys {map {$_ => 1} @array};

Méthode 2: Extension de la méthode 1 pour la réutilisabilité

Mieux vaut créer un sous-programme si nous sommes censés utiliser cette fonctionnalité plusieurs fois dans notre code.

sub get_unique {
    my %seen;
    grep !$seen{$_}++, @_;
}
my @unique = get_unique(@array);

Méthode 3: utiliser le module List::MoreUtils

use List::MoreUtils qw(uniq);
my @unique = uniq(@array);
Kamal Nayan
la source
1

Les réponses précédentes résument assez bien les moyens possibles d'accomplir cette tâche.

Cependant, je suggère une modification pour ceux qui ne se soucient pas de compter les doublons, mais qui se soucient de l'ordre.

my @record = qw( yeah I mean uh right right uh yeah so well right I maybe );
my %record;
print grep !$record{$_} && ++$record{$_}, @record;

Notez que les grep !$seen{$_}++ ...incrémentations suggérées précédemment $seen{$_}avant de négocier, l'incrémentation se produit indépendamment du fait qu'il ait déjà été %seenou non. Ce qui précède, cependant, court-circuit quand $record{$_}est vrai, laissant ce qui a été entendu une fois «hors du %record».

Vous pouvez également opter pour ce ridicule, qui tire parti de l'autovivification et de l'existence de clés de hachage:

...
grep !(exists $record{$_} || undef $record{$_}), @record;

Cela pourrait toutefois prêter à confusion.

Et si vous ne vous souciez ni de l'ordre ni du nombre de doublons, vous pouvez utiliser un autre hack en utilisant des tranches de hachage et l'astuce que je viens de mentionner:

...
undef @record{@record};
keys %record; # your record, now probably scrambled but at least deduped
YenForYang
la source
Pour ceux qui comparent: sub uniq{ my %seen; undef @seen{@_}; keys %seen; } Neat.
stevesliva
0

Essayez ceci, il semble que la fonction uniq ait besoin d'une liste triée pour fonctionner correctement.

use strict;

# Helper function to remove duplicates in a list.
sub uniq {
  my %seen;
  grep !$seen{$_}++, @_;
}

my @teststrings = ("one", "two", "three", "one");

my @filtered = uniq @teststrings;
print "uniq: @filtered\n";
my @sorted = sort @teststrings;
print "sort: @sorted\n";
my @sortedfiltered = uniq sort @teststrings;
print "uniq sort : @sortedfiltered\n";
Saschabeaumont
la source
0

Utilisation du concept de clés de hachage uniques:

my @array  = ("a","b","c","b","a","d","c","a","d");
my %hash   = map { $_ => 1 } @array;
my @unique = keys %hash;
print "@unique","\n";

Sortie: acbd

Sandeep_black
la source