我试图找出一种方法,无需遍历数组就可以检查数组中是否存在值。
我正在读取文件中的参数。我有一长串我不想处理的参数。我将这些不需要的参数放在数组中@badparams
。
我想读取一个新参数,如果不存在@badparams
,请对其进行处理。如果确实存在@badparams
,请转到下一个阅读。
我试图找出一种方法,无需遍历数组就可以检查数组中是否存在值。
我正在读取文件中的参数。我有一长串我不想处理的参数。我将这些不需要的参数放在数组中@badparams
。
我想读取一个新参数,如果不存在@badparams
,请对其进行处理。如果确实存在@badparams
,请转到下一个阅读。
Answers:
只需将数组转换为哈希即可:
my %params = map { $_ => 1 } @badparams;
if(exists($params{$someparam})) { ... }
您还可以在列表中添加更多(唯一)参数:
$params{$newparam} = 1;
然后再返回(唯一)参数列表:
@badparams = keys %params;
1
再次设置为。
最佳通用-尤其是短数组(不超过1000个项目)和编码器,无法确定哪种优化最适合其需求。
# $value can be any regex. be safe
if ( grep( /^$value$/, @array ) ) {
print "found it";
}
曾经提到过,即使数组中的第一个值匹配,grep也会通过所有值。的确如此,但是grep在大多数情况下仍然非常快。如果您谈论的是短数组(少于1000个项目),那么大多数算法无论如何都会非常快。如果您谈论的是非常长的数组(1,000,000个项目),则grep可以很快地接受,无论该项目是数组中的第一个还是中间或最后一个。
更长数组的优化案例:
如果您的数组已排序,请使用“二进制搜索”。
如果多次重复搜索同一数组,请先将其复制到哈希中,然后检查该哈希。如果需要考虑内存,则将每个项目从数组移到哈希中。内存效率更高,但会破坏原始数组。
如果在数组中重复搜索相同的值,则延迟建立缓存。(在搜索每个项目时,首先检查搜索结果是否存储在持久性哈希中。如果在哈希中找不到搜索结果,则搜索数组并将结果放入持久性哈希中,以便下次我们在哈希中找到它,然后跳过搜索)。
注意:这些优化仅在处理长数组时才会更快。不要过度优化。
您可以在Perl 5.10中使用smartmatch功能,如下所示:
对于文字值查找,请执行以下操作。
if ( "value" ~~ @array )
对于标量查找,执行以下操作将如上所述。
if ($val ~~ @array)
对于下面的内联数组,将按上述方式工作。
if ( $var ~~ ['bar', 'value', 'foo'] )
在Perl 5.18中, smartmatch被标记为实验性的,因此您需要通过在脚本/模块中添加以下内容来打开实验性编译指示来关闭警告:
use experimental 'smartmatch';
另外,如果您想避免使用smartmatch,请按照Aaron的说明使用:
if ( grep( /^$value$/, @array ) ) {
#TODO:
}
use experimental 'smartmatch'
。由于我可以控制perl版本(内部系统),因此可以使用以下no warnings 'experimental::smartmatch';
语句。
这篇博客文章讨论了这个问题的最佳答案。
简要来说,如果您可以安装CPAN模块,那么最易读的解决方案是:
any(@ingredients) eq 'flour';
要么
@ingredients->contains('flour');
但是,更常见的习惯用法是:
any { $_ eq 'flour' } @ingredients
但是请不要使用该first()
功能!它根本没有表达代码的意图。请勿使用~~
“智能匹配”运算符:它已损坏。而且不要使用grep()
带有哈希的解决方案:它们会遍历整个列表。
any()
一旦找到您的价值,它就会停止。
查看博客文章以了解更多详细信息。
grep
如果查看资源,请尝试避免使用。
if ( grep( /^$value$/, @badparams ) ) {
print "found";
}
for (@badparams) {
if ($_ eq $value) {
print "found";
last;
}
}
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;
@eakssjo的基准已被破坏-可以在循环中创建散列,而在循环中创建正则表达式。固定版本(加上我添加的List::Util::first
和List::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)
尽管使用起来很方便,但似乎转换为哈希的解决方案要花费很多性能,这对我来说是个问题。
#!/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
速度更快,因为找到匹配项后它将停止迭代。
有两种方法可以执行此操作。如其他文章所建议的那样,您可以将值扔到查找表的哈希中。(我将添加另一个成语。)
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。
@bad_param_lookup{@bad_params} = ()
,但需要使用它exists
来测试成员资格。