Perl: if (Element in Liste)


74

Ich suche nach dem Vorhandensein eines Elements in einer Liste.

In Python gibt es ein inSchlüsselwort und ich würde so etwas tun:

if element in list:
    doTask

Gibt es in Perl etwas Äquivalentes, ohne die gesamte Liste manuell durchlaufen zu müssen?

Antworten:


115

AKTUALISIEREN:

Die Smartmatch-Funktionsfamilie ist jetzt experimentell

Smart Match, das in Version 5.10.0 hinzugefügt und in Version 5.10.1 erheblich überarbeitet wurde, war ein regelmäßiger Beschwerdepunkt. Obwohl es eine Reihe von Möglichkeiten gibt, wie es nützlich ist, hat es sich sowohl für Benutzer als auch für Implementierer von Perl als problematisch und verwirrend erwiesen. Es gab eine Reihe von Vorschlägen, wie das Problem am besten angegangen werden kann. Es ist klar, dass sich Smartmatch mit ziemlicher Sicherheit in Zukunft ändern oder verschwinden wird. Es wird nicht empfohlen, sich auf das aktuelle Verhalten zu verlassen.

Warnungen werden jetzt ausgegeben, wenn der Parser ~~ sieht, gegeben ist oder wann.




Wenn Sie Perl v5.10 nicht benötigen, können Sie eines der folgenden Beispiele verwenden.

  • Der Smart Match~~ Operator.

    if( $element ~~ @list ){ ... }
    if( $element ~~ [ 1, 2, 3 ] ){ ... }
    
  • Sie können auch das Konstrukt given/when verwenden. Welches nutzt die Smart Match-Funktionalität intern.

    given( $element ){
       when( @list ){ ... }
    }
    
  • Sie können eine forSchleife auch als "Topicalizer" verwenden (dh sie wird festgelegt $_).

    for( @elements ){
       when( @list ){ ... }
    }
    

Eine Sache, die in Perl 5.12 herauskommen wird, ist die Fähigkeit, die Post-Fix-Version von zu verwenden when. Das macht es noch ähnlicher ifund unless.

given( $element ){
  ... when @list;
}

Wenn Sie in der Lage sein müssen, auf älteren Perl-Versionen zu laufen, gibt es noch mehrere Optionen.

  • Sie könnten denken, Sie könnten zuerst mit List :: Util :: davonkommen , aber es gibt einige Randbedingungen, die es problematisch machen.

    In diesem Beispiel ist es ziemlich offensichtlich, dass wir erfolgreich gegeneinander antreten wollen 0. Leider wird dieser Code failurejedes Mal gedruckt .

    use List::Util qw'first';
    my $element = 0;
    if( first { $element eq $_ } 0..9 ){
      print "success\n";
    } else {
      print "failure\n";
    }
    

    Sie könnten den Rückgabewert von firstauf Definiertheit überprüfen , aber das schlägt fehl, wenn wir tatsächlich möchten, dass eine Übereinstimmung undefmit erfolgreich ist.

  • Sie können jedoch sicher verwenden grep.

    if( grep { $element eq $_ } 0..9 ){ ... }
    

    Dies ist sicher, da es grepin einem skalaren Kontext aufgerufen wird. Arrays geben die Anzahl der Elemente zurück, wenn sie im skalaren Kontext aufgerufen werden. Das wird also auch dann weiter funktionieren, wenn wir versuchen, gegen uns anzutreten undef.

  • Sie könnten eine umschließende forSchleife verwenden. Stellen Sie einfach sicher, dass Sie anrufen last, um die Schleife bei einem erfolgreichen Match zu verlassen. Andernfalls wird Ihr Code möglicherweise mehrmals ausgeführt.

    for( @array ){
      if( $element eq $_ ){
        ...
        last;
      }
    }
    
  • Sie könnten die forSchleife in den Zustand der ifAnweisung einfügen ...

    if(
      do{
        my $match = 0;
        for( @list ){
          if( $element eq $_ ){
            $match = 1;
            last;
          }
        }
        $match; # the return value of the do block
      }
    ){
      ...
    }
    
  • ... aber es könnte klarer sein, die forSchleife vor die ifAnweisung zu setzen.

    my $match = 0;
    for( @list ){
      if( $_ eq $element ){
        $match = 1;
        last;
      }
    }
    
    if( $match ){ ... }
    
  • Wenn Sie nur mit Zeichenfolgen übereinstimmen, können Sie auch einen Hash verwenden. Dies kann Ihr Programm beschleunigen, wenn @listes groß ist und Sie %hashmehrmals gegeneinander antreten . Vor allem, wenn @arraysich nichts ändert, denn dann muss man nur einmal laden %hash.

    my %hash = map { $_, 1 } @array;
    if( $hash{ $element } ){ ... }
    
  • Sie können auch Ihre eigene Unterroutine erstellen. Dies ist einer der Fälle, in denen es nützlich ist, Prototypen zu verwenden .

    sub in(&@){
      local $_;
      my $code = shift;
      for( @_ ){ # sets $_
        if( $code->() ){
          return 1;
        }
      }
      return 0;
    }
    
    if( in { $element eq $_ } @list ){ ... }
    

@BradGilbert Gute Arbeit!
Gaußblurinc

3
@xxxxxxx Du sagst das, aber meine Antworten mit den höchsten Stimmen sind im Allgemeinen auch die längsten.
Brad Gilbert

@BradGilbert wow! Ich mag Betreiber ~~! es ist so wellig und hilfreich! :)
Gaussblurinc

5
Beachten Sie, dass "gegeben", "wann" und der "Smart Match Operator " seit Perl 5.18 als "experimentell" markiert sind und Warnungen generieren. Der Smart Match ~~ Link der Antwort enthält ebenfalls kein #Smart-matching-in-detailFragment mehr.
Peter V. Mørch

Bei der nächsten Bearbeitung sollte der Smart Match-Link auf perldoc.perl.org/perlop.html#Smartmatch-Operator
Brad Gilbert,


9

Wenn Sie dies mehrmals planen, können Sie den Speicherplatz gegen die Suchzeit austauschen:

#!/usr/bin/perl

use strict; use warnings;

my @array = qw( one ten twenty one );
my %lookup = map { $_ => undef } @array;

for my $element ( qw( one two three ) ) {
    if ( exists $lookup{ $element }) {
        print "$element\n";
    }
}

Angenommen, die Häufigkeit, mit der das Element angezeigt wird, @arrayist nicht wichtig und der Inhalt von @arrayist ein einfacher Skalar.


1
Gute Technik, die definitiv erwähnenswert ist.
Jrockway

Gute Technik mit der Erwähnung, dass es sich nur auszahlt, wenn mehrere Suchvorgänge durchgeführt werden. +1
xxxxxxx

9

List :: Util :: first

$foo = first { ($_ && $_ eq "value" } @list;    # first defined value in @list

Oder für handrollende Typen:

my $is_in_list = 0;
foreach my $elem (@list) {
    if ($elem && $elem eq $value_to_find) {
        $is_in_list = 1;
        last;
    }
}
if ($is_in_list) {
   ...

Eine etwas andere Version könnte auf sehr langen Listen etwas schneller sein:

my $is_in_list = 0;
for (my $i = 0; i < scalar(@list); ++$i) {
    if ($list[i] && $list[i] eq $value_to_find) {
        $is_in_list = 1;
        last;
    }
}
if ($is_in_list) {
   ...

das ist ziemlich halbherzig. -1
xxxxxxx

7

List :: MoreUtils

Bei Perl> = 5.10 ist der Smart Match Operator sicherlich der einfachste Weg, wie viele andere bereits gesagt haben.

Bei älteren Perl-Versionen würde ich stattdessen List :: MoreUtils :: any vorschlagen .

List::MoreUtils ist kein Kernmodul (manche sagen, es sollte sein), aber es ist sehr beliebt und in den wichtigsten Perl-Distributionen enthalten.

Es hat folgende Vorteile:

  • es gibt true / false zurück (wie es Python intut) und nicht den Wert des Elements, wie es List::Util::firsttut (was das Testen schwierig macht, wie oben erwähnt);
  • Im Gegensatz grepdazu stoppt es beim ersten Element, das den Test besteht (auch die Kurzschlüsse des Smart Match Operators von Perl ).
  • es funktioniert mit jeder Perl-Version (naja,> = mindestens 5.00503).

Hier ist ein Beispiel, das mit jedem gesuchten (skalaren) Wert funktioniert, einschließlich undef:

use List::MoreUtils qw(any);

my $value = 'test'; # or any other scalar
my @array = (1, 2, undef, 'test', 5, 6);

no warnings 'uninitialized';

if ( any { $_ eq $value } @array ) {
    print "$value present\n"
}

PS

(Im Produktionscode ist es besser, den Umfang von einzuschränken no warnings 'uninitialized').


1
Beachten Sie, dass der "Smart Match Operator " seit Perl 5.18 als "experimentell" markiert ist und Warnungen generiert. Der Smart Match ~~ Link der Antwort enthält ebenfalls kein #Smart-matching-in-detailFragment mehr.
Peter V. Mørch

List::Utilscheint auch zu haben any.
Andy Mikhaylenko

6

TIMTOWTDI

sub is (&@) {
  my $test = shift;
  $test->() and return 1 for @_;
  0
}

sub in (@) {@_}

if( is {$_ eq "a"} in qw(d c b a) ) {
  print "Welcome in perl!\n";
}

Ich mag es nicht, ich hasse Perl.
Hynek-Pichi-Vychodil

5

grep ist hier hilfreich

if (grep { $_ eq $element } @list) {
    ....
}

6
List::Util::firstist wahrscheinlich ein etwas effizienterer Weg, dies zu tun.
Jrockway

1
oder weitaus effizienter, wenn @list eine signifikante Größe hat, da List :: Util :: first nach dem ersten Match nicht fortgesetzt wird, grep jedoch.
MkV

4
Ich habe dies mit einer großen Liste getestet und beide sind ziemlich schnell. Als der Geschwindigkeitsunterschied spürbar wurde, hatte mein Computer 6 GB RAM verbraucht. Wenn Ihre Liste qw (foo bar baz) ist, spielt es wahrscheinlich keine Rolle.
Jrockway

Abgesehen von großen Datenmengen, bei denen sich das übereinstimmende Element nicht am Ende befindet, ist dies eine gute und einfache Möglichkeit, die Antwort zu erhalten.
Mleykamp

1
@jrockway Ist aber List::Util::firstkein Drop-In-Ersatz für grep. Da grep hier im skalaren Kontext aufgerufen wird, erhalten Sie die Anzahl der übereinstimmenden Elemente. Wenn Sie List::Util::firststattdessen verwenden, können Sie ein Element abrufen, @listdas als falsch verglichen wird ...
Josch

3

Wahrscheinlich Perl6::Junctionist der klarste Weg zu tun. Keine XS-Abhängigkeiten, kein Durcheinander und keine neue Perl-Version erforderlich.

use Perl6::Junction qw/ any /;

if (any(@grant) eq 'su') {
    ...
}

2

Dieser Blog-Beitrag beschreibt die besten Antworten auf diese Frage.

Kurz gesagt, wenn Sie CPAN-Module installieren können, sind die besten Lösungen:

if any(@ingredients) eq 'flour';

oder

if @ingredients->contains('flour');

Eine üblichere Redewendung ist jedoch:

if @any { $_ eq 'flour' } @ingredients

was ich weniger klar finde.

Aber bitte nicht die first () Funktion benutzen! Es drückt überhaupt nicht die Absicht Ihres Codes aus. Verwenden Sie nicht den Operator "Smart Match": Er ist defekt. Verwenden Sie weder grep () noch die Lösung mit einem Hash: Sie durchlaufen die gesamte Liste. Während any () stoppt, sobald es Ihren Wert findet.

Weitere Informationen finden Sie im Blog-Beitrag.

PS: Ich antworte für Leute, die in Zukunft die gleiche Frage haben werden.


1
Ich denke, die übliche Redewendung ist if any { $_ eq 'flour' } @ingredients, aber man muss sich daran erinnern use List::MoreUtils qw/ any /;.
Gpojd

2

Sie können in Perl eine ausreichend ähnliche Syntax erreichen, wenn Sie Autoload- Hacking durchführen.

Erstellen Sie ein kleines Paket für das automatische Laden:

package Autoloader;
use strict;
use warnings;

our $AUTOLOAD;

sub AUTOLOAD {
    my $self     = shift;
    my ($method) = (split(/::/, $AUTOLOAD))[-1];

    die "Object does not contain method '$method'" if not ref $self->{$method} eq 'CODE';

    goto &{$self->{$method}};
}

1;

Dann enthält Ihr anderes Paket oder Hauptskript eine Unterroutine, die das gesegnete Objekt zurückgibt, das von Autoload verarbeitet wird, wenn seine Methode versucht wird, aufgerufen zu werden.

sub element {
    my $elem = shift;

    my $sub = {
        in => sub {
            return if not $_[0];

            # you could also implement this as any of the other suggested grep/first/any solutions already posted.
            my %hash; @hash{@_} = ();
            return (exists $hash{$elem}) ? 1 : ();
        }
    };

    bless($sub, 'Autoloader');
}

Dies lässt Sie mit der Verwendung wie folgt aussehen:

doTask if element('something')->in(@array);

Wenn Sie den Abschluss und seine Argumente neu organisieren, können Sie die Syntax in die andere Richtung ändern, damit er so aussieht, was dem Autobox-Stil etwas näher kommt:

doTask if search(@array)->contains('something');

Funktion, um das zu tun:

sub search {
    my @arr = @_;

    my $sub = {
        contains => sub {
            my $elem = shift or return;
            my %hash; @hash{@arr} = ();
            return (exists $hash{$elem}) ? 1 : ();
        }
    };

    bless($sub, 'Autoloader');
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.