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


239

我试图找出一种方法,无需遍历数组就可以检查数组中是否存在值。

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

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


3
记录下来,答案确实取决于您的情况。听起来好像您想重复查找,所以使用jkramer建议的哈希值很好。如果您只想进行一次查找,则最好进行迭代。(而且在某些情况下,您可能希望二进制搜索而不是使用哈希!)
Cascabel 2010年


6
根据记录(这可能完全不适用于您的情况),通常最好是识别“良好价值”并忽略其余部分,而不是尝试清除已知的“不良价值”。您需要问的问题是,是否有可能存在一些您尚不了解的错误值。
Grant McLean 2010年

Answers:


187

只需将数组转换为哈希即可:

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

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

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

$params{$newparam} = 1;

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

@badparams = keys %params;

38
作为记录,此代码仍会遍历数组。map {}调用仅使该迭代非常容易键入。
肯尼·怀兰德

3
仅当@badparams中的值是伪静态的并且您打算对地图进行大量检查时,我才这样做。我不建议您一次检查。
亚伦·T·哈里斯

这难道不适合具有多个具有相同值的项目的数组吗?
罗伯·威尔斯

3
@RobWells不,它将正常工作。下次看到相同的值时,它将覆盖哈希中的条目,在这种情况下,它将1再次设置为。
andrewrjones

222

最佳通用-尤其是短数组(不超过1000个项目)和编码器,无法确定哪种优化最适合其需求。

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

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

更长数组的优化案例:

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

如果多次重复搜索同一数组,请先将其复制到哈希中,然后检查该哈希。如果需要考虑内存,则将每个项目从数组移到哈希中。内存效率更高,但会破坏原始数组。

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

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


12
Perl 5.10中引入了双波浪号
已暂停,直到另行通知。

15
@DennisWilliamson ... 在5.18中被认为是实验性的
Xaerxess

5
避免在生产代码中进行智能匹配。这是不稳定的/实验性的,有待进一步通知。
矢量Gorgoth

1
我发现它也更具可读性,但是请勿使用表示它效率不高,并且会检查每个元素(即使它是第一个元素)。
giordano 2013年

7
不要使用if(“ value” ~~ @array)。~~是一项称为Smartmatch的实验功能。该实验似乎被视为失败,将在Perl的将来版本中被删除或修改。
yahermann '17

120

您可以在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:
}

4
这很好,但是似乎对Perl 5.10来说是新的。花了一些时间才弄清楚为什么会出现语法错误。
Igor Skochinsky 2014年

17
警告:您可能要避免这种情况,因为操作员在不同版本中的行为显然不同,并且同时被标记为实验。因此,除非您完全控制perl版本(以及拥有该版本的人),否则应该避免使用它。
迷宫2015年

1
我喜欢关于为什么建议设置的解释use experimental 'smartmatch'。由于我可以控制perl版本(内部系统),因此可以使用以下no warnings 'experimental::smartmatch';语句。
lepe 2016年

43

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

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

any(@ingredients) eq 'flour';

要么

@ingredients->contains('flour');

但是,更常见的习惯用法是:

any { $_ eq 'flour' } @ingredients

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

any() 一旦找到您的价值,它就会停止。

查看博客文章以了解更多详细信息。


8
任何需求use List::Util qw(any);List::Util核心模块中
Onlyjob

13

方法1:grep(在将值预期为正则表达式时可能要小心)。

grep如果查看资源,请尝试避免使用。

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

方法2:线性搜索

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

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

12

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

6
如果要测试多个元素,则提前创建哈希可以节省您的时间。但是,如果您只是想知道它是否包含单个元素,那么您就没有哈希了。因此,创建散列应该是计算时间的一部分。对于正则表达式更是如此:对于每个要查找的元素,您都需要一个新的regexp。
fishinear 2013年

1
@fishinear是的,但是,如果您只对一项检查感兴趣,而对多项检查则没有兴趣,那么显然是微优化,甚至想知道哪种方法更快,因为这些微秒无关紧要。如果您想重做此检查,则必须使用散列,因为一旦创建散列的成本很小,就可以忽略。以上基准衡量不同的测试方式,不包括任何设置。是的,这在您的用例中可能是无效的,但是再次重申-如果您仅进行一次检查,则应使用对您和您的伴侣最易读的内容。
Xaerxess 2015年

10

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

#!/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
使用List::Util::first速度更快,因为找到匹配项后它将停止迭代。
RobEarl 2012年

1
-1您的基准有缺陷,grep显著慢于创建哈希和做的查找,因为前者是O(n)和后者O(1)。只需执行一次哈希创建(在循环外),然后预计算正则表达式即可仅测量方法(请参见我的回答)。
Xaerxess

4
@Xaerxess:就我而言,我想进行一次查找,因此我认为对哈希/正则表达式的创建和查找/ grep进行计数是公平的。它的任务是进行许多查找,我想您的解决方案更好。
aksel

3
如果您只想进行一次迭代,则您选择的任何方法之间的区别都是无法区分的,因此任何基准测试都是错误的,因为在这种情况下,这是一种邪恶的微优化。
Xaerxess

2
正则表达式仅编译一次,因为它带有标志“ o”。
Apoc

3

@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开始的值这里您可以放置​​任何正则表达式


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


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


1
您也可以编写@bad_param_lookup{@bad_params} = (),但需要使用它exists来测试成员资格。
格雷格·培根

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.