web-dev-qa-db-fra.com

Perl: if (élément dans la liste)

Je recherche la présence d'un élément dans une liste.

Dans Python il y a un mot clé in et je ferais quelque chose comme:

if element in list:
    doTask

Existe-t-il quelque chose d'équivalent en Perl sans avoir à parcourir manuellement toute la liste?

68
Jonathan

MISE À JOUR:

La famille de fonctionnalités smartmatch est maintenant expérimentale

La correspondance intelligente, ajoutée dans la v5.10.0 et considérablement révisée dans la v5.10.1, a été un point de plainte régulier. Bien qu'il existe un certain nombre de façons dont il est utile, il s'est également révélé problématique et déroutant pour les utilisateurs et les implémenteurs de Perl. Il y a eu un certain nombre de propositions sur la meilleure façon de résoudre le problème. Il est clair que smartmatch va presque certainement changer ou disparaître à l'avenir. Il n'est pas recommandé de se fier à son comportement actuel.

Des avertissements seront désormais émis lorsque l'analyseur verra ~~, donné ou quand.




Si vous pouvez vous passer de l'exigence de Perl v5.10, vous pouvez utiliser l'un des exemples suivants.

  • Le match intelligent ~~ opérateur.

    if( $element ~~ @list ){ ... }
    if( $element ~~ [ 1, 2, 3 ] ){ ... }
    
  • Vous pouvez également utiliser la construction given/when . Qui utilise la fonctionnalité de correspondance intelligente en interne.

    given( $element ){
       when( @list ){ ... }
    }
    
  • Vous pouvez également utiliser une boucle for comme "topicalizer" (ce qui signifie qu'elle définit $_).

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

Une chose qui sortira dans Perl 5.12 est la possibilité d'utiliser la version post-fix de when. Ce qui le rend encore plus semblable à if et unless.

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

Si vous devez pouvoir exécuter sur des versions plus anciennes de Perl, il existe encore plusieurs options.

  • Vous pourriez penser que vous pouvez vous débrouiller en utilisant List :: Util :: first , mais certaines conditions Edge le rendent problématique.

    Dans cet exemple, il est assez évident que nous voulons réussir la correspondance avec 0. Malheureusement, ce code imprimera failure à chaque fois.

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

    Vous pouvez vérifier la valeur de retour de first pour la définition, mais cela échouera si nous voulons réellement qu'une correspondance avec undef réussisse.

  • Cependant, vous pouvez utiliser grep en toute sécurité.

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

    C'est sûr car grep est appelé dans un contexte scalaire. Les tableaux renvoient le nombre d'éléments lorsqu'ils sont appelés dans un contexte scalaire. Donc, cela continuera de fonctionner même si nous essayons de nous comparer à undef.

  • Vous pouvez utiliser une boucle for englobante. Assurez-vous simplement d'appeler last, pour quitter la boucle en cas de correspondance réussie. Sinon, vous pourriez finir par exécuter votre code plus d'une fois.

    for( @array ){
      if( $element eq $_ ){
        ...
        last;
      }
    }
    
  • Vous pouvez placer la boucle for dans la condition de l'instruction if ...

    if(
      do{
        my $match = 0;
        for( @list ){
          if( $element eq $_ ){
            $match = 1;
            last;
          }
        }
        $match; # the return value of the do block
      }
    ){
      ...
    }
    
  • ... mais il pourrait être plus clair de placer la boucle for avant l'instruction if.

    my $match = 0;
    for( @list ){
      if( $_ eq $element ){
        $match = 1;
        last;
      }
    }
    
    if( $match ){ ... }
    
  • Si vous ne comparez que des chaînes, vous pouvez également utiliser un hachage. Cela peut accélérer votre programme si @list est grand et , vous allez faire la comparaison avec %hash plusieurs fois. En particulier si @array ne change pas, car il suffit alors de charger %hash une fois que.

    my %hash = map { $_, 1 } @array;
    if( $hash{ $element } ){ ... }
    
  • Vous pouvez également créer votre propre sous-programme. C'est l'un des cas où il est utile d'utiliser prototypes .

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

~~ est "l'opérateur de correspondance intelligente", et fait plus que simplement répertorier la détection d'appartenance.

15
jrockway

List :: Util :: first

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

Ou pour les types roulants à la main:

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) {
   ...

Une version légèrement différente POURRAIT être un peu plus rapide sur de très longues listes:

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) {
   ...
9
DVK

Si vous prévoyez de le faire plusieurs fois, vous pouvez échanger de l'espace contre du temps de recherche:

#!/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";
    }
}

en supposant que le nombre de fois où l'élément apparaît dans @array n'est pas important et le contenu de @array sont de simples scalaires.

8
Sinan Ünür

Liste :: MoreUtils

Sur Perl> = 5.10, l'opérateur de correspondance intelligente est sûrement le moyen le plus simple, comme beaucoup d'autres l'ont déjà dit.

Sur les anciennes versions de Perl, je suggérerais plutôt List :: MoreUtils :: any .

List::MoreUtils n'est pas un module de base (certains disent qu'il devrait l'être) mais il est très populaire et il est inclus dans les principales distributions Perl.

Il présente les avantages suivants:

  • il renvoie vrai/faux (comme le fait in de Python) et non la valeur de l'élément, comme List::Util::first does (ce qui le rend difficile à tester, comme indiqué ci-dessus);
  • contrairement à grep, il s'arrête au premier élément qui réussit le test (l'opérateur de correspondance intelligente de Perl court-circuits également);
  • cela fonctionne avec n'importe quelle version de Perl (enfin,> = 5.00503 au moins).

Voici un exemple qui fonctionne avec n'importe quelle valeur recherchée (scalaire), y compris 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"
}

P.S.

(Dans le code de production, il est préférable de réduire la portée de no warnings 'uninitialized').

7
emazep

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";
}

grep est utile ici

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

Probablement Perl6::Junction est la façon la plus claire de le faire. Aucune dépendance XS, aucun gâchis et aucune nouvelle version Perl requise.

use Perl6::Junction qw/ any /;

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

Ce billet de blog discute des meilleures réponses à cette question.

En résumé, si vous pouvez installer des modules CPAN, les meilleures solutions sont:

if any(@ingredients) eq 'flour';

ou

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

Cependant, un idiome plus habituel est:

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

que je trouve moins clair.

Mais n'utilisez pas la fonction first ()! Il n'exprime pas du tout l'intention de votre code. N'utilisez pas l'opérateur "Smart match": il est cassé. Et n'utilisez pas grep () ni la solution avec un hachage: ils parcourent toute la liste. Alors que any () s'arrêtera dès qu'il trouvera votre valeur.

Consultez le billet de blog pour plus de détails.

PS: je réponds aux personnes qui auront la même question à l'avenir.

2
mascip

Vous pouvez accomplir une syntaxe suffisamment similaire en Perl si vous faites du piratage Autoload .

Créez un petit package pour gérer le chargement automatique:

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;

Ensuite, votre autre package ou script principal contiendra un sous-programme qui retourne l'objet béni qui est géré par Autoload lorsque sa méthode tente d'être appelée.

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');
}

Cela vous laisse avec une utilisation ressemblant à:

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

Si vous réorganisez la fermeture et ses arguments, vous pouvez changer la syntaxe dans l'autre sens pour la faire ressembler à ceci, qui est un peu plus proche du style de la boîte automatique:

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

fonction pour le faire:

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');
}
2
delias