Wie entferne ich doppelte Elemente aus einem Array in Perl?

156

Ich habe ein Array in Perl:

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

Wie entferne ich die Duplikate aus dem Array?

David
quelle

Antworten:

168

Sie können so etwas tun, wie in perlfaq4 gezeigt :

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

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

print "@filtered\n";

Ausgänge:

one two three

Wenn Sie ein Modul verwenden möchten, probieren Sie die uniqFunktion von ausList::MoreUtils

Greg Hewgill
quelle
28
Bitte verwenden Sie nicht $ a oder $ b in Beispielen, da es sich um die magischen Globals von sort () handelt
szabgab
2
Es ist ein mylexikalische in diesem Bereich, so ist es in Ordnung. Davon abgesehen könnte möglicherweise ein aussagekräftigerer Variablenname gewählt werden.
Ephemient
2
@ephemient ja, aber wenn Sie in dieser Funktion Sortierung hinzufügen würden , würde es trumpfen $::aund $::b, nicht wahr ?
Vol7ron
5
@BrianVandenberg Willkommen in der Welt von 1987 - als diese erstellt wurde - und fast 100% Backword-Kompatibilität für Perl - daher kann sie nicht beseitigt werden.
Szabgab
18
sub uniq { my %seen; grep !$seen{$_}++, @_ }ist eine bessere Implementierung, da die Ordnung kostenlos erhalten bleibt. Oder noch besser, verwenden Sie die von List :: MoreUtils.
Ikegami
120

Die Perl-Dokumentation enthält eine schöne Sammlung von FAQs. Ihre Frage wird häufig gestellt:

% perldoc -q duplicate

Die Antwort, die aus der Ausgabe des obigen Befehls kopiert und eingefügt wurde, wird unten angezeigt:

Gefunden in /usr/local/lib/perl5/5.10.0/pods/perlfaq4.pod
 Wie kann ich doppelte Elemente aus einer Liste oder einem Array entfernen?
   (beigetragen von brian d foy)

   Verwenden Sie einen Hash. Wenn Sie die Wörter "einzigartig" oder "dupliziert" denken, denken Sie
   "Hash-Schlüssel".

   Wenn Sie sich nicht um die Reihenfolge der Elemente kümmern, können Sie einfach
   Erstellen Sie den Hash und extrahieren Sie die Schlüssel. Es ist nicht wichtig, wie du
   Erstellen Sie diesen Hash: Nur dass Sie "Schlüssel" verwenden, um die eindeutigen Elemente zu erhalten.

       mein% hash = map {$ _, 1} @array;
       # oder ein Hash-Slice: @hash {@array} = ();
       # oder ein foreach: $ hash {$ _} = 1 foreach (@array);

       mein @unique = keys% hash;

   Wenn Sie ein Modul verwenden möchten, versuchen Sie die Funktion "uniq" von
   "List :: MoreUtils". Im Listenkontext werden die eindeutigen Elemente zurückgegeben.
   Beibehaltung ihrer Reihenfolge in der Liste. Im skalaren Kontext wird das zurückgegeben
   Anzahl der eindeutigen Elemente.

       benutze List :: MoreUtils qw (uniq);

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

   Sie können auch jedes Element durchgehen und die Elemente überspringen, die Sie gesehen haben
   Vor. Verwenden Sie einen Hash, um den Überblick zu behalten. Das erste Mal, wenn die Schleife eine sieht
   Element, dieses Element hat keinen Schlüssel in% Seen. Die "nächste" Anweisung erstellt
   der Schlüssel und verwendet sofort seinen Wert, der "undef" ist, also die Schleife
   fährt mit dem "Drücken" fort und erhöht den Wert für diese Taste. Der nächste
   Wenn die Schleife dasselbe Element sieht, existiert ihr Schlüssel im Hash und
   Der Wert für diesen Schlüssel ist wahr (da er nicht 0 oder "undef" ist)
   next überspringt diese Iteration und die Schleife geht zum nächsten Element.

       mein @unique = ();
       mein% gesehen = ();

       foreach mein $ elem (@array)
       {
         next if $ found {$ elem} ++;
         push @unique, $ elem;
       }}

   Sie können dies mit einem grep kurz schreiben, was dasselbe tut
   Ding.

       mein% gesehen = ();
       mein @unique = grep {! $ found {$ _} ++} @array;
John Siracusa
quelle
17
John iz in mah anzers stiehlt mah rep!
Brian D Foy
5
Ich denke, Sie sollten Bonuspunkte erhalten, wenn Sie die Frage tatsächlich nachschlagen.
Brad Gilbert
2
Ich mag, dass die beste Antwort 95% Copy-Paste und 3 Sätze OC ist. Um ganz klar zu sein, dies ist die beste Antwort; Ich finde diese Tatsache einfach amüsant.
Parthian Shot
70

Installieren Sie List :: MoreUtils von CPAN

Dann in Ihrem 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
quelle
4
Die Tatsache, dass List :: MoreUtils nicht mit Perl gebündelt ist, beeinträchtigt die Portabilität von Projekten, die es verwenden :( (ich
jedenfalls
3
@ Ranguard: @dup_listsollte innerhalb des uniqAnrufs sein, nicht@dups
incutonez
@yassinphilip CPAN ist eines der Dinge, die Perl so leistungsfähig und großartig wie möglich machen. Wenn Sie Ihre Projekte nur auf Basis von Kernmodulen schreiben, setzen Sie Ihrem Code eine enorme Grenze, zusammen mit möglicherweise vollständig geschriebenem Code, der versucht, das zu tun, was einige Module viel besser können, nur um ihre Verwendung zu vermeiden. Die Verwendung von Kernmodulen garantiert auch nichts, da verschiedene Perl-Versionen Kernmodule zur Distribution hinzufügen oder daraus entfernen können, sodass die Portabilität weiterhin davon abhängt.
Francisco Zarabozo
24

Meine übliche Vorgehensweise ist:

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

Wenn Sie einen Hash verwenden und die Elemente zum Hash hinzufügen. Sie haben auch den Bonus zu wissen, wie oft jedes Element in der Liste angezeigt wird.

Xetius
quelle
2
Dies hat den Nachteil, dass die ursprüngliche Bestellung nicht beibehalten wird, wenn Sie sie benötigen.
Nathan Fellman
Es ist besser, Slices anstelle von foreachLoop zu verwenden:@unique{@myarray}=()
Onlyjob
8

Die Variable @array ist die Liste mit doppelten Elementen

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

Kann mit einem einfachen Perl One Liner durchgeführt werden.

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.

Der PFM-Block führt Folgendes aus:

Daten in @in werden in MAP eingespeist. MAP erstellt einen anonymen Hash. Schlüssel werden aus dem Hash extrahiert und in @out eingegeben

Falke
quelle
4

Das letzte war ziemlich gut. Ich würde es nur ein bisschen optimieren:

my @arr;
my @uniqarr;

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

Ich denke, dies ist wahrscheinlich der am besten lesbare Weg, dies zu tun.

jh314
quelle
4

Methode 1: Verwenden Sie einen Hash

Logik: Ein Hash kann nur eindeutige Schlüssel haben. Iterieren Sie also über das Array, weisen Sie jedem Element des Arrays einen beliebigen Wert zu und behalten Sie das Element als Schlüssel für diesen Hash bei. Geben Sie die Schlüssel des Hashs zurück, es ist Ihr einzigartiges Array.

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

Methode 2: Erweiterung von Methode 1 zur Wiederverwendbarkeit

Es ist besser, eine Unterroutine zu erstellen, wenn wir diese Funktionalität in unserem Code mehrmals verwenden sollen.

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

Methode 3: Modul verwenden List::MoreUtils

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

Frühere Antworten fassen die möglichen Wege zur Erfüllung dieser Aufgabe ziemlich gut zusammen.

Allerdings schlage ich eine Änderung für diejenigen , die nicht über Pflege zählen die Duplikate, aber nicht kümmern uns um Ordnung.

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

Beachten Sie, dass die zuvor vorgeschlagenen grep !$seen{$_}++ ...Inkremente $seen{$_}vor dem Negieren ausgeführt werden, sodass das Inkrement unabhängig davon erfolgt, ob es bereits vorhanden war %seenoder nicht. Das oben Gesagte schließt jedoch kurz, wenn $record{$_}es wahr ist, und lässt das, was einmal gehört wurde, "aus %record".

Sie könnten sich auch für diese Lächerlichkeit entscheiden, die die Autovivifizierung und das Vorhandensein von Hash-Schlüsseln ausnutzt:

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

Dies könnte jedoch zu Verwirrung führen.

Und wenn Sie sich weder für die Reihenfolge noch für die doppelte Anzahl interessieren, können Sie für einen weiteren Hack Hash-Slices und den Trick verwenden, den ich gerade erwähnt habe:

...
undef @record{@record};
keys %record; # your record, now probably scrambled but at least deduped
YenForYang
quelle
Für diejenigen, die vergleichen: sub uniq{ my %seen; undef @seen{@_}; keys %seen; } Ordentlich.
stevesliva
0

Versuchen Sie dies, anscheinend benötigt die Uniq-Funktion eine sortierte Liste, um ordnungsgemäß zu funktionieren.

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
quelle
0

Verwenden des Konzepts eindeutiger Hash-Schlüssel:

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

Ausgabe: acbd

Sandeep_black
quelle