Perl: if ( element in list )

I'm looking for presence of an element in a list.

In Python there is an in keyword and I would do something like:

if element in list:
doTask

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

106745 次浏览

在这里 grep 是有帮助的

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

~~是“智能匹配操作符”,并且不仅仅是列出成员检测。

列表: : Util: : first

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

或者是手卷式的:

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

一个稍微不同的版本可能在很长的列表中会更快一些:

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

如果计划多次执行此操作,则可以为查找时间权衡空间:

#!/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 that the number of times the element appears in @array is not important and the contents of @array are simple scalars.

更新:

目前,智能匹配系列功能正处于试验阶段

在 v5.10.0中添加的智能匹配和在 v5.10.1中显著修改的智能匹配一直是人们经常抱怨的问题。尽管它有许多有用的方法,但它也被证明对 Perl 的用户和实现者都是有问题和混乱的。关于如何最好地解决这个问题,已经有许多建议。很明显,智能匹配几乎肯定会在未来发生改变或者消失。不建议依赖其当前行为。

现在,当解析器看到 ~ ~ 、给定或者什么时候发出警告。




如果您不需要 Perl v5.10,那么可以使用下面的任何示例。

  • 智能匹配 ~~接线员。

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

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

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

One thing that will come out in Perl 5.12 is the ability to use the post-fix version of when. Which makes it even more like if and unless.

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

If you have to be able to run on older versions of Perl, there still are several options.

  • 您可能认为您可以使用 列表: : Util: : first,但是有一些边缘条件使它成为问题。

    In this example it is fairly obvious that we want to successfully match against 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";
    }
    

    您可以检查 first的返回值是否已定义,但是如果我们实际上希望与 undef匹配成功,那么这将失败。

  • You can safely use grep however.

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

    这是安全的,因为 grep是在标量上下文中调用的。数组在标量上下文中调用时返回元素数。因此,这将继续工作,即使我们尝试匹配对 undef

  • 您可以使用一个封闭的 for循环。只要确保调用 last,就可以在成功匹配时退出循环。否则,您可能会不止一次地运行代码。

    for( @array ){
    if( $element eq $_ ){
    ...
    last;
    }
    }
    
  • You could put the for loop inside the condition of the if statement ...

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

    my $match = 0;
    for( @list ){
    if( $_ eq $element ){
    $match = 1;
    last;
    }
    }
    
    
    if( $match ){ ... }
    
  • If you're only matching against strings, you could also use a hash. This can speed up your program if @list is large and, you are going to match against %hash several times. Especially if @array doesn't change, because then you only have to load up %hash once.

    my %hash = map { $_, 1 } @array;
    if( $hash{ $element } ){ ... }
    
  • You could also make your own subroutine. This is one of the cases where it is useful to use prototypes.

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

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

列表: : MoreUtils

在 perl > = 5.10时,正如许多人已经说过的,智能匹配运算符肯定是最简单的方法。

对于旧版本的 perl,我建议使用 列表: : MoreUtils: : any

List::MoreUtils不是核心模块(有人说应该是) ,但它非常流行,并且包含在主要的 perl 发行版中。

它具有以下优点:

  • 它返回 true/false (就像 Python 的 in那样) ,而不是像 List::Util::first那样返回元素的值(这使得它很难测试,如上所述) ;
  • unlike grep, it stops at the first element which passes the test (perl's smart match operator 短路 as well);
  • 它适用于任何 perl 版本(至少 > = 5.00503)。

Here is an example which works with any searched (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"
}

附言。

(在生产代码中,最好缩小 no warnings 'uninitialized'的范围)。

Probably Perl6::Junction is the clearest way to do. No XS dependencies, no mess and no new perl version required.

use Perl6::Junction qw/ any /;


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

如果进行一些 自动加载修改,您可以在 Perl 中完成类似的足够多的语法。

创建一个小包来处理自动加载:

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;

然后您的其他包或主脚本将包含一个子例程,该子例程返回 Autoload 在其方法尝试被调用时处理的受保护对象。

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

这使你的用法看起来像:

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

如果你重新组织闭包和它的参数,你可以用另一种方式来切换语法,使它看起来像这样,这有点像 autobox 风格:

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

功能:

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