【问题标题】:How can I check if a Perl array contains a particular value?如何检查 Perl 数组是否包含特定值?
【发布时间】:2010-05-18 19:03:31
【问题描述】:

我正在尝试找出一种方法来检查数组中是否存在值,而无需遍历数组。

我正在读取一个参数文件。我有一长串不想处理的参数。我将这些不需要的参数放在数组@badparams中。

我想读取一个新参数,如果它在@badparams 中不存在,则处理它。如果@badparams中确实存在,则进入下一个阅读。

【问题讨论】:

  • 为了记录,答案确实取决于您的情况。听起来您想进行重复查找,因此使用 jkramer 建议的哈希是好的。如果您只想进行一次查找,则不妨进行迭代。 (并且在某些情况下,您可能想要二进制搜索而不是使用哈希!)
  • 为了记录(这可能完全不适用于您的情况),识别“好的价值观”并忽略其余部分而不是试图清除已知的“坏价值观”通常是一个更好的主意。您需要问的问题是,是否可能存在一些您还不知道的不良价值观。

标签: perl arrays comparison


【解决方案1】:

最佳通用 - 尤其是短数组(1000 项或更少)和不确定哪种优化最适合其需求的编码人员。

# $value can be any regex. be safe
if ( grep( /^$value$/, @array ) ) {
  print "found it";
}

已经提到,即使数组中的第一个值匹配,grep 也会遍历所有值。确实如此,但是 grep 在大多数情况下仍然非常快。如果您谈论的是短数组(少于 1000 个项目),那么大多数算法无论如何都会非常快。如果您谈论的是非常长的数组(1,000,000 个项目),则无论该项目是数组中的第一个、中间还是最后一个,grep 的速度都可以接受。

更长数组的优化案例:

如果您的数组已排序,请使用“二分查找”。

如果同一个数组被重复搜索很多次,先拷贝成一个hash,再检查hash。如果内存是一个问题,则将数组中的每个项目移动到散列中。内存效率更高,但会破坏原始数组。

如果在数组中重复搜索相同的值,则懒惰地构建缓存。 (在搜索每个项目时,首先检查搜索结果是否存储在持久哈希中。如果在哈希中找不到搜索结果,则搜索数组并将结果放入持久哈希中,以便下次我们将在哈希中找到它并跳过搜索)。

注意:这些优化只会在处理长数组时更快。不要过度优化。

【讨论】:

  • 双波浪号是在 Perl 5.10 中引入的
  • @DennisWilliamson ...和in 5.18 it's considered experimantal
  • 在生产代码中避免智能匹配。这是不稳定的/实验性的等待进一步通知。
  • 我发现它也更具可读性,但Do not use 说它效率不高,并且会检查每个元素,即使它是第一个元素。
  • 不要使用 if ("value" ~~ @array)。 ~~ 是一个名为 Smartmatch 的实验性功能。该实验似乎被视为失败,将在 Perl 的未来版本中删除或修改。
【解决方案2】:

只需将数组变成哈希:

my %params = map { $_ => 1 } @badparams;

if(exists($params{$someparam})) { ... }

您还可以向列表中添加更多(唯一)参数:

$params{$newparam} = 1;

然后得到一个(唯一的)参数列表:

@badparams = keys %params;

【讨论】:

  • 为了记录,这段代码仍然遍历数组。 map{} 调用只是使该迭代非常易于键入。
  • 只有当您在@badparams 中的值是伪静态的并且您计划对地图进行大量检查时,我才会这样做。我不建议单次检查。
  • 对于包含多个具有相同值的项目的数组来说,这不会爆炸吗?
  • @RobWells 不,它会正常工作。下次它看到相同的值时,它只会覆盖哈希中的条目,在这种情况下,它会再次将其设置为 1
【解决方案3】:

您可以在 Perl 5.10 中使用 smartmatch 功能,如下所示:

对于文字值查找,如下所示即可。

if ( "value" ~~ @array ) 

对于标量查找,下面的操作将与上面一样。

if ($val ~~ @array)

对于下面的内联数组,将像上面一样工作。

if ( $var ~~ ['bar', 'value', 'foo'] ) 

Perl 5.18 中,smartmatch 被标记为实验性,因此您需要通过在脚本/模块中添加以下内容来打开 experimental pragma 来关闭警告:

use experimental 'smartmatch';

或者,如果您想避免使用 smartmatch - 然后正如 Aaron 所说,使用:

if ( grep( /^$value$/, @array ) ) {
  #TODO:
}

【讨论】:

  • 这很好,但对 Perl 5.10 来说似乎是新的。花了一些时间才弄清楚为什么会出现语法错误。
  • 警告:你可能想避免这个,因为运营商在不同的版本中有明显不同的行为,同时也是marked as experimental。因此,除非您完全控制您的 perl 版本(以及谁拥有该版本),否则您应该避免使用它。
  • 我喜欢this explanation 关于为什么推荐设置use experimental 'smartmatch'。因为我可以控制我的 perl 版本(内部系统),所以我使用 no warnings 'experimental::smartmatch'; 语句。
【解决方案4】:

方法 1:grep(当 value 应该是正则表达式时可能要小心)。

如果查看资源,请尽量避免使用grep

if ( grep( /^$value$/, @badparams ) ) {
  print "found";
}

方法二:线性搜索

for (@badparams) {
    if ($_ eq $value) {
       print "found";
       last;
    }
}

方法 3:使用哈希

my %hash = map {$_ => 1} @badparams;
print "found" if (exists $hash{$value});

方法四:智能匹配

(在 Perl 5.10 中添加,在 Perl 5.18 中标记为实验性)。

use experimental 'smartmatch';  # for perl 5.18
print "found" if ($value ~~ @badparams);

方法五:使用模块List::MoreUtils

use List::MoreUtils qw(any);
@badparams = (1,2,3);
$value = 1;
print "found" if any {$_ == $value} @badparams;

【讨论】:

    【解决方案5】:

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

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

    any(@ingredients) eq 'flour';
    

    @ingredients->contains('flour');
    

    然而,一个更常见的成语是:

    any { $_ eq 'flour' } @ingredients
    

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

    any() 将在找到您的值后立即停止。

    查看博文了解更多详情。

    【讨论】:

    • any 需要use List::Util qw(any);List::UtilCore modules 中。
    • 为了澄清起见,contains 不是 CPAN 提供的内置函数,博客只是展示了如何实现它。
    【解决方案6】:

    如果您需要知道数组中每个元素的数量,除了该元素的存在,您可以使用

    my %bad_param_lookup;
    @bad_param_lookup{ @bad_params } = ( 1 ) x @bad_params;
    %bad_param_lookup = map { $_ => $bad_param_lookup{$_}++} @bad_params;
    

    然后对于@bad_params 中的每个$i,$bad_param_lookup{$i} 包含@bad_params 中的$i 数量

    【讨论】:

      【解决方案7】:

      @eakssjo's benchmark 已损坏 - 衡量在循环中创建哈希与在循环中创建正则表达式。固定版本(加上我添加了List::Util::firstList::MoreUtils::any):

      use List::Util qw(first);
      use List::MoreUtils qw(any);
      use Benchmark;
      
      my @list = ( 1..10_000 );
      my $hit = 5_000;
      my $hit_regex = qr/^$hit$/; # precompute regex
      my %params;
      $params{$_} = 1 for @list;  # precompute hash
      timethese(
          100_000, {
              'any' => sub {
                  die unless ( any { $hit_regex } @list );
              },
              'first' => sub {
                  die unless ( first { $hit_regex } @list );
              },
              'grep' => sub {
                  die unless ( grep { $hit_regex } @list );
              },
              'hash' => sub {
                  die unless ( $params{$hit} );
              },
          });
      

      结果(这是 100_000 次迭代,比 @eakssjo 的回答多十倍):

      Benchmark: timing 100000 iterations of any, first, grep, hash...
             any:  0 wallclock secs ( 0.67 usr +  0.00 sys =  0.67 CPU) @ 149253.73/s (n=100000)
           first:  1 wallclock secs ( 0.63 usr +  0.01 sys =  0.64 CPU) @ 156250.00/s (n=100000)
            grep: 42 wallclock secs (41.95 usr +  0.08 sys = 42.03 CPU) @ 2379.25/s (n=100000)
            hash:  0 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU) @ 10000000.00/s (n=100000)
                  (warning: too few iterations for a reliable count)
      

      【讨论】:

      • 如果您想测试多个元素,那么提前创建哈希可以节省您的时间。但是,如果您只想知道它是否包含单个元素,那么您还没有散列。因此,创建哈希应该是计算时间的一部分。对于正则表达式更是如此:您需要为每个要查找的元素创建一个新的正则表达式。
      • @fishinear 是的,但是如果您只对一次检查而不是多次检查感兴趣,那么显然它是微优化,甚至想知道哪种方法更快,因为那些微秒无关紧要。 如果你想重做这个检查,散列是要走的路,因为创建散列的成本小到可以忽略。上述基准衡量不同的测试方式,不包括任何设置。是的,这在您的用例中可能无效,但同样 - 如果您只进行一次检查,您应该使用对您和您的伙伴最易读的任何内容。
      【解决方案8】:

      @files 是一个现有的数组

      my @new_values =  grep(/^2[\d].[\d][A-za-z]?/,@files);
      
      print join("\n", @new_values);
      
      print "\n";
      

      /^2[\d].[\d][A-za-z]?/ = vaues 从 2 开始可以放任何正则表达式

      【讨论】:

        【解决方案9】:

        尽管使用起来很方便,但转换为哈希的解决方案似乎消耗了相当多的性能,这对我来说是个问题。

        #!/usr/bin/perl
        use Benchmark;
        my @list;
        for (1..10_000) {
            push @list, $_;
        }
        
        timethese(10000, {
          'grep'    => sub {
                    if ( grep(/^5000$/o, @list) ) {
                        # code
                    }
                },
          'hash'    => sub {
                    my %params = map { $_ => 1 } @list;
                    if ( exists($params{5000}) ) {
                        # code
                    }
                },
        });
        

        基准测试的输出:

        Benchmark: timing 10000 iterations of grep, hash...
                  grep:  8 wallclock secs ( 7.95 usr +  0.00 sys =  7.95 CPU) @ 1257.86/s (n=10000)
                  hash: 50 wallclock secs (49.68 usr +  0.01 sys = 49.69 CPU) @ 201.25/s (n=10000)
        

        【讨论】:

        • 使用List::Util::first 更快,因为它会在找到匹配项时停止迭代。
        • -1 您的基准测试存在缺陷,grep 比创建哈希和查找要慢显着,因为前者是 O(n),后者是 O(1)。只需创建一次哈希(在循环外)并预先计算正则表达式以仅测量方法(see my answer)。
        • @Xaerxess:就我而言,我想做 one 查找,所以我认为计算哈希/正则表达式创建和查找/grep 是公平的。任务是进行多次查找,我想您的解决方案更好。
        • 如果您只想进行一次迭代,那么您选择的任何方法之间的差异都无法区分,因此任何基准测试都是错误的,因为在这种情况下它是一种邪恶的微优化。
        • 正则表达式只编译一次,因为它有标志'o'。
        【解决方案10】:

        你当然想要这里的哈希。将错误的参数作为键放入哈希中,然后确定哈希中是否存在特定参数。

        our %bad_params = map { $_ => 1 } qw(badparam1 badparam2 badparam3)
        
        if ($bad_params{$new_param}) {
          print "That is a bad parameter\n";
        }
        

        如果您真的对使用数组感兴趣,请查看 List::UtilList::MoreUtils

        【讨论】:

          【解决方案11】:

          有两种方法可以做到这一点。正如其他帖子所建议的那样,您可以使用将值放入散列中以进行查找表。 (我将添加另一个成语。)

          my %bad_param_lookup;
          @bad_param_lookup{ @bad_params } = ( 1 ) x @bad_params;
          

          但是如果它的数据主要是单词字符而不是太多的元数据,你可以把它转储到一个正则表达式中:

          use English qw<$LIST_SEPARATOR>;
          
          my $regex_str = do { 
              local $LIST_SEPARATOR = '|';
              "(?:@bad_params)";
           };
          
           # $front_delim and $back_delim being any characters that come before and after. 
           my $regex = qr/$front_delim$regex_str$back_delim/;
          

          此解决方案必须针对您正在寻找的“坏值”类型进行调整。再说一次,它可能完全不适合某些类型的字符串,所以 caveat emptor

          【讨论】:

          • 您也可以写@bad_param_lookup{@bad_params} = (),但您需要使用exists 来测试会员资格。
          【解决方案12】:
          my @badparams = (1,2,5,7,'a','zzz');
          
          my $badparams = join('|',@badparams);   # '|' or any other character not present in params
          
          foreach my $par (4,5,6,7,'a','z','zzz')
          {
              if ($badparams =~ /\b$par\b/)
              {
                  print "$par is present\n";
              }
              else
              {
                  print "$par is not present\n";
              }
          }
          

          您可能需要检查数字前导空格的一致性

          【讨论】:

            猜你喜欢
            • 2015-06-24
            • 2016-02-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-02-11
            • 2020-05-24
            • 1970-01-01
            • 2021-05-03
            相关资源
            最近更新 更多