Perl:if(list中的元素)


75

我正在寻找列表中某个元素的存在。

在Python中有一个in关键字,我会做类似的事情:

if element in list:
    doTask

Perl中是否有等效的东西,而不必手动遍历整个列表?

Answers:


115

更新:

Smartmatch系列功能现已处于试验阶段

在v5.10.0中添加并在v5.10.1中进行了重大修改的智能匹配一直是人们经常抱怨的问题。尽管有很多方法可以使用它,但事实证明,它对Perl的用户和实现者都是有问题和令人困惑的。关于如何最好地解决该问题,已经有许多建议。显然,Smartmatch几乎肯定会在未来发生变化或消失。不建议依赖其当前行为。

现在,当解析器看到~~,给定或何时发出警告。




如果您可以摆脱对Perl v5.10的要求,那么可以使用以下任何示例。

  • 智能匹配~~操作。

    if( $element ~~ @list ){ ... }
    if( $element ~~ [ 1, 2, 3 ] ){ ... }
    
  • 您也可以使用given/when构造。内部使用智能匹配功能。

    given( $element ){
       when( @list ){ ... }
    }
    
  • 您还可以将for循环用作“ topicalizer”(表示设置$_)。

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

在Perl 5.12中将会出现的一件事是使用的修订后版本的能力when。这使得它更像ifunless

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

如果必须能够在较旧版本的Perl上运行,则仍有多种选择。

  • 您可能认为可以使用List :: Util :: first来摆脱困境,但是有一些边缘条件使它有问题。

    在此示例中,很明显我们要成功匹配0。不幸的是,此代码failure每次都会打印。

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

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

  • 您可以放心使用grep

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

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

  • 您可以使用封闭for循环。只要确保您致电last,就可以在成功的比赛中退出循环。否则,您可能最终会多次运行代码。

    for( @array ){
      if( $element eq $_ ){
        ...
        last;
      }
    }
    
  • 您可以将for循环放入if语句的条件内...

    if(
      do{
        my $match = 0;
        for( @list ){
          if( $element eq $_ ){
            $match = 1;
            last;
          }
        }
        $match; # the return value of the do block
      }
    ){
      ...
    }
    
  • ...但是将for循环放在if语句之前可能更清楚。

    my $match = 0;
    for( @list ){
      if( $_ eq $element ){
        $match = 1;
        last;
      }
    }
    
    if( $match ){ ... }
    
  • 如果只匹配字符串,则还可以使用哈希。如果@list较大这可以加快程序的速度,您将需要%hash多次匹配。尤其是如果@array它没有变化,那么您只需要加载%hash一次即可。

    my %hash = map { $_, 1 } @array;
    if( $hash{ $element } ){ ... }
    
  • 您也可以创建自己的子例程。这是使用原型有用的情况之一。

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

3
@xxxxxxx你这么说,但我投票得最高的答案通常也是最长的。
布拉德·吉尔伯特

@BradGilbert哇!我喜欢接线员~~!它是如此波浪和有用!:)
gaussblurinc

5
请注意,自perl 5.18起,“给定”,“何时”和“智能匹配运算符”现在被标记为“实验性”,并且将生成警告。答案的智能匹配~~链接也不再有任何#Smart-matching-in-detail片段。
Peter V.Mørch2014年

接下来的编辑应将智能匹配链接切换到perldoc.perl.org/perlop.html#Smartmatch-Operator
Brad Gilbert


9

如果您打算多次执行此操作,则可以权衡查找时间的空间:

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

假定元素出现的次数@array并不重要,并且其内容@array是简单的标量。


1
好的技术绝对值得一提。
jrockway 2010年

提及只有在进行多次查找后才能获得回报的好技术。+1
xxxxxxx 2010年

9

列表::实用程序::第一

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

这是半定的。-1
xxxxxxx 2010年

7

列表::更多实用工具

在perl> = 5.10上,智能匹配运算符无疑是最简单的方法,正如许多其他人已经说过的那样。

在旧版本的perl上,我建议使用List :: MoreUtils :: any

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

具有以下优点:

  • 它返回true / false(就像Pythonin一样),而不是返回元素的值List::Util::first(就像上面提到的那样,这使得很难测试);
  • 不像grep,它停在通过测试的第一个元素上(perl的智能匹配运算符也短路);
  • 它适用于任何perl版本(至少> = 5.00503)。

这是一个适用于任何搜索(标量)值的示例,包括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')。


1
请注意,自perl 5.18起,“智能匹配运算符”现在已标记为“实验性”,并且会生成警告。答案的智能匹配~~链接也不再有任何#Smart-matching-in-detail片段。
Peter V.Mørch2014年

List::Util似乎也有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";
}

我不喜欢它,我讨厌Perl。
海尼克

5

grep 在这里有帮助

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

6
List::Util::first可能是一种更有效的方法。
jrockway 2010年

1
如果@list的大小很大,则效率会大大提高,因为List :: Util :: first不会在第一个匹配项之后继续,但是grep会继续。
MkV

4
我用了大量的清单进行了测试,而且两者都非常快。到速度差异明显的时候,我的机器已经烧毁了6 GB的RAM。如果您的列表是qw(foo bar baz),那可能没什么大不了的。
jrockway 2010年

除了匹配元素不在尽头的大型数据集之外,这是获得答案的一种简便/便捷的方法。
mleykamp 2010年

1
@jrockway但List::Util::first不是的替代品grep。由于grep在标量上下文中被调用,因此您可以获得匹配元素的数量。如果List::Util::first改用,则可能会检索一个元素,@list该元素的比较结果为false ...
josch 2016年

3

可能Perl6::Junction是最清晰的方法。没有XS依赖项,没有混乱,也不需要新的Perl版本。

use Perl6::Junction qw/ any /;

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

2

这篇博客文章讨论了这个问题的最佳答案。

简要来说,如果可以安装CPAN模块,那么最好的解决方案是:

if any(@ingredients) eq 'flour';

要么

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

但是,更常见的习惯用法是:

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

我发现不太清楚。

但是请不要使用first()函数!它根本没有表达代码的意图。不要使用“智能匹配”运算符:它已损坏。而且不要使用带有哈希的grep()或解决方案:它们会遍历整个列表。虽然any()将在找到您的值后立即停止。

查看博客文章以了解更多详细信息。

PS:我正在为将来会遇到相同问题的人解答。


1
我认为通常的习惯用法是if any { $_ eq 'flour' } @ingredients,但是您必须记住use List::MoreUtils qw/ any /;
gpojd

2

如果您进行了一些自动加载黑客攻击,则可以在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');
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.