Perl: if (item in a list)

I am looking for the presence of an item in a list.

Python has the in keyword, and I would do something like:

 if element in list: doTask 

Is there something equivalent in Perl without having to manually iterate over the entire list?

+60
arrays perl
Mar 04 '10 at 23:32
source share
10 answers

UPDATE:

The smartmatch feature family is now experimental

The smart match added in v5.10.0 and significantly reworked in version 5.10.1 is a regular complaint. Although there are several ways in which this is useful, it has also proven to be problematic and confusing for both users and Perl developers. A number of suggestions were proposed on how to best solve this problem. It is clear that smartmatch will almost certainly either change or leave in the future. It is not recommended to rely on its current behavior.

Now warnings will be issued when the parser sees ~~ given or when.










If you may need Perl v5.10, you can use any of the following examples.

  • The smart match operator ~~ .

     if( $element ~~ @list ){ ... } if( $element ~~ [ 1, 2, 3 ] ){ ... } 
  • You can also use the given / when construct. Which uses the internal functionality of smart matching.

     given( $element ){ when( @list ){ ... } } 
  • You can also use the for loop as a "topicalizer" (meaning that it sets $_ ).

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

One thing that will be released in Perl 5.12 is the ability to use the post-functional version of when . This makes it even more like if and unless .

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



If you need to work on older versions of Perl, there are still a few options.

  • You might think you can get away with List :: Util :: first , but there are some boundary conditions that make it problematic.

    In this example, it is pretty obvious that we want to successfully match with 0 . Unfortunately, this code will print failure every time.

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

    You can check the return value of first for certainty, but that will not succeed if we really want a match with undef for success.

  • You can safely use grep .

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

    This is safe because grep is called in a scalar context. Arrays return the number of elements when called in a scalar context. This way, it will continue to work even if we try to map to undef .

  • You can use a closed for loop. Just make sure you call last to exit the loop in a successful match. Otherwise, you can run the code more than once.

     for( @array ){ if( $element eq $_ ){ ... last; } } 
  • You can put the for loop in the state of the if ...

     if( do{ my $match = 0; for( @list ){ if( $element eq $_ ){ $match = 1; last; } } $match; # the return value of the do block } ){ ... } 
  • ... but it may be clearer to put the for loop before the if .

     my $match = 0; for( @list ){ if( $_ eq $element ){ $match = 1; last; } } if( $match ){ ... } 
  • If you use only strings, you can also use a hash. This can speed up your program if @list large and , you will be matching several times with %hash . Especially if @array does not change, because then you only need to load %hash once.

     my %hash = map { $_, 1 } @array; if( $hash{ $element } ){ ... } 
  • You can also create your own routine. This is one of the cases where prototypes are useful.

     sub in(&@){ local $_; my $code = shift; for( @_ ){ # sets $_ if( $code->() ){ return 1; } } return 0; } if( in { $element eq $_ } @list ){ ... } 
+98
Mar 05 '10 at 8:38
source share
 if( $element ~~ @list ){ do_task } 

~~ is an “intelligent matching operator” and not only recognizes list membership.

+15
Mar 04 '10 at 23:35
source share

List :: Util :: first

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

Or for types of manual rolling:

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

A slightly different version MAY be somewhat faster on a very long list:

 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
Mar 04 '10 at 23:55
source share

If you plan to do this many times, you can exchange the space for the search time:

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

assuming the number of elements appearing in @array is not important, and the contents of @array are simple scalars.

+8
Mar 05 2018-10-10T00:
source share

TIMTOWTDI

 sub is (&@) { my $test = shift; $test->() and return 1 for @_; 0 } sub in (@) {@_} if( is {$_ eq "a"} in qw(dcba) ) { print "Welcome in perl!\n"; } 
+6
Mar 05
source share

List :: MoreUtils

In perl> = 5.10, the smart match operator is by far the easiest way, as many others have said.

In older versions of perl, I would suggest List :: MoreUtils :: any .

List::MoreUtils not the main module (some say it should be), but it is very popular and is included in the main perl distributions.

It has the following advantages:

  • returns true / false (as Python in does), and not the value of the element, since List::Util::first does (which makes testing difficult, as indicated above);
  • unlike grep , it stops at the first element that passes the test (as well as short circuits of the perl smart match operator);
  • it works with any version of perl (well,> = 5.00503).

Here is an example that works with any search (scalar) value, including 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

(In production code, it is better to narrow down the scope of no warnings 'uninitialized' ).

+6
Nov 24 2018-11-11T00:
source share

grep is useful here

 if (grep { $_ eq $element } @list) { .... } 
+5
Mar 04 '10 at 23:33
source share

Perl6::Junction is probably the clearest way to do it. No XS dependencies, clutter, or new perl required.

 use Perl6::Junction qw/ any /; if (any(@grant) eq 'su') { ... } 
+3
Aug 24 '16 at 17:20
source share

This blog post discusses the best answers to this question.

As a brief summary, if you can install CPAN modules, the best solutions are:

 if any(@ingredients) eq 'flour'; 

or

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

However, the more common idiom is:

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

which I find less clear.

But please do not use the first () function! It does not reflect the intent of your code at all. Do not use the "Smart match" statement: it is broken. And don't use grep () or a hash solution: they iterate over the entire list. Although any () will stop as soon as it finds your value.

Read more on the blog.

PS: I am responsible for people who will have the same question in the future.

+1
May 22 '13 at 12:02
source share

You can do the same syntax in Perl if you do Autoload hack.

Create a small package to handle startup:

 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; 

Then your other package or main script will contain a routine that returns the object that Autoload processes when its method tries to call.

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

This gives you the opportunity to look like this:

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

If you reorganize the closure and its arguments, you can switch the syntax in another way so that it looks a little closer to the autobox style:

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

to do this:

 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'); } 
+1
Jan 27 '17 at 10:14
source share



All Articles