如何检查Perl数组是否包含特定值?

时间:2010-05-18 19:03:32

标签: perl arrays comparison

我试图找出一种检查数组中是否存在值而不迭代数组的方法。

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

我想阅读一个新参数,如果@badparams中不存在,请对其进行处理。如果@badparams中确实存在,请转到下一个阅读。

12 个答案:

答案 0 :(得分:206)

最佳通用目的 - 特别是短阵列(1000个或更少项目)和编码器,不确定哪种优化最适合他们的需求。

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

有人提到,即使数组中的第一个值匹配,grep也会遍历所有值。这是事实,但是对于大多数情况来说, grep仍然非常快。如果你在谈论短阵列(少于1000个项目),那么大多数算法都会非常快。如果您正在谈论非常长的数组(1,000,000个项目),无论该项目是数组中的第一个还是中间或最后一个,grep都是可以接受的快速。

更长阵列的优化案例:

如果您的数组已排序,请使用“二进制搜索”。

如果重复搜索相同的数组,首先将其复制到哈希中,然后检查哈希值。如果内存是一个问题,那么将每个项目从数组移动到哈希。内存效率更高但会破坏原始阵列。

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

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

答案 1 :(得分:177)

只需将数组转换为哈希:

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

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

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

$params{$newparam} = 1;

后来得到一份(独特的)参数列表:

@badparams = keys %params;

答案 2 :(得分:114)

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

对于字面值查找,执行以下操作即可。

if ( "value" ~~ @array ) 

对于标量查找,执行以下操作将如上所述。

if ($val ~~ @array)

对于下面的内联数组,将按上述方式工作。

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

Perl 5.18 中,smartmatch被标记为实验性,因此您需要通过在脚本/模块下面添加以下experimental编译指示来关闭警告:

use experimental 'smartmatch';

或者如果你想避免使用smartmatch - 那么就像Aaron所说:

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

答案 3 :(得分:42)

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

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

any(@ingredients) eq 'flour';

@ingredients->contains('flour');

然而,更常见的习语是:

any { $_ eq 'flour' } @ingredients

但请不要使用first()功能!它根本不表达您的代码的意图。不要使用~~“智能匹配”操作符:它已损坏。并且不要使用grep()或使用散列的解决方案:它们遍历整个列表。

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

查看博客文章了解更多详情。

答案 4 :(得分:11)

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

#!/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)

答案 5 :(得分:10)

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

答案 6 :(得分:5)

方法1:grep(可能需要小心,而值应该是正则表达式)。

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

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

方法2:线性搜索

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

方法3:使用哈希

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

方法4:smartmatch

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

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

方法5:使用核心模块List::MoreUtils

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

答案 7 :(得分:2)

你当然想要哈希。将坏参数作为键放在哈希中,然后确定哈希中是否存在特定参数。

our %bad_params = map { $_ => 1 } qw(badparam1 badparam2 badparam3)

if ($bad_params{$new_param}) {
  print "That is a bad parameter\n";
}

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

答案 8 :(得分:2)

@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 :(得分:1)

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

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

答案 10 :(得分:0)

有两种方法可以做到这一点。您可以使用将值抛出到查找表的哈希中,如其他帖子所建议的那样。 (我只会添加另一个成语。)

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/;

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

答案 11 :(得分:-1)

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

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