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?
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.
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;
}
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 ){ ... }
if( $element ~~ @list ){
do_task
}
~~
est "l'opérateur de correspondance intelligente", et fait plus que simplement répertorier la détection d'appartenance.
$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) {
...
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.
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:
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);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);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"
}
(Dans le code de production, il est préférable de réduire la portée de no warnings 'uninitialized'
).
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) {
....
}
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') {
...
}
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.
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');
}