Perl:如果(列表中的元素)

Posted

技术标签:

【中文标题】Perl:如果(列表中的元素)【英文标题】:Perl: if ( element in list ) 【发布时间】:2011-01-23 22:04:43 【问题描述】:

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

在 Python 中有一个 in 关键字,我会这样做:

if element in list:
    doTask

在 Perl 中有没有等价的东西而不必手动遍历整个列表?

【问题讨论】:

【参考方案1】:

更新:

The smartmatch family of features are now experimental

在 v5.10.0 中添加并在 v5.10.1 中进行了重大修订的智能匹配一直是投诉的常规点。尽管它有很多有用的方式,但它也被证明对于 Perl 的用户和实现者来说是有问题的和令人困惑的。关于如何最好地解决这个问题,已经提出了许多建议。很明显,smartmatch 几乎肯定会在未来改变或消失。不建议依赖其当前行为。

现在,当解析器看到 ~~、given 或 when 时会发出警告。




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

smart match ~~ 运算符。

if( $element ~~ @list ) ... 
if( $element ~~ [ 1, 2, 3 ] ) ... 

您也可以使用given/when 构造。它在内部使用智能匹配功能。

given( $element )
   when( @list ) ... 

您还可以将 for 循环用作“主题化器”(意味着它设置了 $_)。

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

您也可以制作自己的子程序。这是使用prototypes 很有用的情况之一。

sub in(&@)
  local $_;
  my $code = shift;
  for( @_ ) # sets $_
    if( $code->() )
      return 1;
    
  
  return 0;


if( in  $element eq $_  @list ) ... 

【讨论】:

@xxxxxxx 你这么说,但我投票最高的答案通常也是最长的。 @BradGilbert 哇!我喜欢运营商~~!它是如此波浪和乐于助人! :) 请注意,“given”、“when”和“smart match operator”现在是 marked as "experimental" since perl 5.18 并将生成警告。答案的smart match ~~ 链接也不再有任何#Smart-matching-in-detail 片段。 下一次编辑应该将智能匹配链接切换到perldoc.perl.org/perlop.html#Smartmatch-Operator【参考方案2】:
if( $element ~~ @list )
   do_task

~~ 是“智能匹配算子”,它不仅仅做列表成员检测。

【讨论】:

请注意,“智能匹配运算符”现在是 marked as "experimental" since perl 5.18 并将生成警告。答案的smart match ~~ 链接也不再有任何#Smart-matching-in-detail 片段。【参考方案3】:

如果您打算多次执行此操作,您可以用空间换取查找时间:

#!/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【参考方案4】:

List::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) 
   ...

【讨论】:

这简直是半途而废。 -1【参考方案5】:

grep 在这里很有帮助

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

【讨论】:

List::Util::first 可能是一种更有效的方法。 或者效率更高,如果@list 很大,因为 List::Util::first 不会继续超过第一个匹配,但 grep 会。 我用一个大列表对此进行了测试,两者都非常快。当速度差异明显时,我的机器已经烧毁了 6 GB 的 RAM。如果您的列表是 qw(foo bar baz),则可能无关紧要。 除了匹配元素未接近末尾的大型数据集外,这是获得答案的好方法。 @jrockway 但是List::Util::first 不是grep 的直接替代品。由于 grep 在这里是在标量上下文中调用的,因此您将获得匹配元素的数量。如果您改用List::Util::first,那么您可能会检索到比较为假的@list 元素...【参考方案6】:

列表::MoreUtils

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

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

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

具有以下优点:

它返回真/假(就像 Python 的 in 所做的那样)而不是元素的值,就像 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'的范围)。

【讨论】:

请注意,“智能匹配运算符”现在是 marked as "experimental" since perl 5.18 并将生成警告。答案的smart match ~~ 链接也不再有任何#Smart-matching-in-detail 片段。 List::Util 似乎也有any【参考方案7】:

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。【参考方案8】:

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

use Perl6::Junction qw/ any /;

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

【讨论】:

【参考方案9】:

This blog post 讨论了这个问题的最佳答案。

作为一个简短的总结,如果您可以安装 CPAN 模块,那么最好的解决方案是:

if any(@ingredients) eq 'flour';

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

不过,更常用的成语是:

if @any  $_ eq 'flour'  @ingredients

我觉得不太清楚。

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

查看博文了解更多详情。

PS:我正在为将来有同样问题的人回答。

【讨论】:

我认为通常的成语是if any $_ eq 'flour' @ingredients,但你必须记住use List::MoreUtils qw/ any /;【参考方案10】:

如果你做一些Autoload hacking,你可以在 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);

如果你重新组织闭包和它的参数,你可以换一种方式转换语法,让它看起来像这样,有点接近自动框样式:

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

【讨论】:

以上是关于Perl:如果(列表中的元素)的主要内容,如果未能解决你的问题,请参考以下文章

perl语言入门总结-第3章-列表与数组

Perl Sort函数用法总结和使用实例

Perl List::Util模块用法详解

Perl中的数组介绍

Perl 在生信中的应用 | 3. 数组

如果缺少,Python将元素添加到列表中的列表