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

我试图找出一种方法来检查数组中值的存在,而不遍历数组。

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

我想读取一个新参数,如果它在@badparams中不存在,则处理它。如果它在@badparams中存在,则转到下一次读取。

493226 次浏览

简单地将数组转换为哈希:

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


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

你也可以添加更多(唯一的)参数到列表中:

$params{$newparam} = 1;

然后返回一个(唯一的)参数列表:

@badparams = keys %params;

这里肯定需要一个哈希。将坏的参数作为键放在散列中,然后决定某个特定参数是否存在于散列中。

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


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

如果你真的对数组感兴趣,可以参考List::UtilList::MoreUtils

有两种方法。您可以使用将值扔到查找表的散列中,正如其他文章所建议的那样。(我再加一个成语。)

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

这个解决方案必须针对您正在寻找的“坏值”类型进行调优。同样,对于某些类型的字符串,它可能完全不合适,因此购者自慎

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

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

前面提到过,即使数组中的第一个值匹配,grep也会遍历所有值。这是真的,然而Grep在大多数情况下仍然非常快。如果你谈论的是短数组(少于1000项),那么大多数算法无论如何都会非常快。如果您谈论的是非常长的数组(1,000,000个项),无论项是数组中的第一个、中间还是最后一个,grep都是可以接受的。

更长的数组优化案例:

如果你的数组是有序的,使用“二分搜索”。

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

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

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

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

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

@eakssjo基准是坏的-衡量在循环中创建哈希值和在循环中创建正则表达式。固定版本(加上我已经添加了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)

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

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

any(@ingredients) eq 'flour';

@ingredients->contains('flour');

然而,更常见的习语是:

any { $_ eq 'flour' } @ingredients

但是请不要使用first()函数!它根本没有表达你代码的意图。不要使用~~ "Smart match"操作符:它被破坏了。不要使用grep()或带散列的解决方案:它们遍历整个列表。

any()将停止一旦它找到你的值。

查看博客文章了解更多细节。

你可以在Perl 5.10中使用smartmatch特性,如下所示:

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

if ( "value" ~~ @array )

对于标量查找,执行以下操作将与上述工作一样。

if ($val ~~ @array)

对于内联数组做下面,将工作如上。

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

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

use experimental 'smartmatch';

或者,如果你想避免使用smartmatch,那么就像Aaron说的那样使用:

if ( grep( /^$value$/, @array ) ) {
#TODO:
}
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";
}
}

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

方法1:grep(可能小心,而值是一个正则表达式)。

如果查看资源,尽量避免使用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});

方法四: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);
@badparams = (1,2,3);
$value = 1;
print "found" if any {$_ == $value} @badparams;

@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]吗?/ =从2开始的值这里可以放入任何正则表达式

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

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的数量