Perl';在数组中搜索标量时,是否使用smartmatch运算符?

Perl';在数组中搜索标量时,是否使用smartmatch运算符?,perl,smartmatch,Perl,Smartmatch,我想在一个不变的数组中重复搜索值 到目前为止,我一直是这样做的:我把值放在一个散列中(因此我有一个数组和一个内容基本相同的散列),然后使用exists搜索散列 我不喜欢两个不同的变量(数组和散列)都存储相同的东西;但是,哈希搜索速度要快得多 我发现Perl5.10中有一个~(smartmatch)操作符。在数组中搜索标量的效率有多高?对于少量可能的匹配项,速度要快,但不能快于散列。哈希确实是测试集合成员资格的正确工具。由于散列访问是O(log n),阵列上的smartmatch仍然是O(n)线性

我想在一个不变的数组中重复搜索值

到目前为止,我一直是这样做的:我把值放在一个散列中(因此我有一个数组和一个内容基本相同的散列),然后使用
exists
搜索散列

我不喜欢两个不同的变量(数组和散列)都存储相同的东西;但是,哈希搜索速度要快得多


我发现Perl5.10中有一个
~
(smartmatch)操作符。在数组中搜索标量的效率有多高?

对于少量可能的匹配项,速度要快,但不能快于散列。哈希确实是测试集合成员资格的正确工具。由于散列访问是O(log n),阵列上的smartmatch仍然是O(n)线性扫描(尽管与grep不同,是短路),允许的匹配中的值数量较多,因此smartmatch的性能相对较差

基准代码(与3个值匹配): 基准结果: “智能匹配”中的“智能”与搜索无关。它是基于上下文在正确的时间做正确的事情


循环遍历数组或索引到散列中是否更快,这是您必须进行基准测试的问题,但一般来说,它必须是一个非常小的数组,才能比索引到散列中更快。

如果要搜索数组中的单个标量,可以先使用的
子例程。它一知道答案就停止了。如果你已经有了哈希,我不认为这比哈希查找要快,但是当你考虑创建哈希并把它存储在内存中时,你就可以更方便地搜索你已经拥有的数组。
至于智能匹配操作员的智能,如果您想了解它有多智能,请测试它。:)

您至少要检查三个案例。最糟糕的情况是,你想找到的每个元素都在最后。最好的情况是,您要查找的每个元素都在开始处。可能的情况是,你想找到的元素平均在中间。

现在,在我开始这个基准测试之前,我希望如果智能匹配可以短路(它可以短路;其记录在中),那么最好的情况时间将保持不变,尽管阵列大小不同,而其他情况会越来越糟糕。如果它不能短路,并且每次都必须扫描整个阵列,那么时间应该没有差别,因为每个案例都涉及相同的工作量

这里有一个基准:

#!perl
use 5.12.2;
use strict;
use warnings;

use Benchmark qw(cmpthese);

my @hits = qw(A B C);
my @base = qw(one two three four five six) x ( $ARGV[0] || 1 );

my @at_end       = ( @base, @hits );
my @at_beginning = ( @hits, @base );

my @in_middle = @base;
splice @in_middle, int( @in_middle / 2 ), 0, @hits;

my @random = @base;
foreach my $item ( @hits ) {
    my $index = int rand @random;
    splice @random, $index, 0, $item;
    }

sub count {
    my( $hits, $candidates ) = @_;

    my $count;
    foreach ( @$hits ) { when( $candidates ) { $count++ } }
    $count;
    }

cmpthese(-5, {
    hits_beginning => sub { my $count = count( \@hits, \@at_beginning ) },
    hits_end       => sub { my $count = count( \@hits, \@at_end ) },
    hits_middle    => sub { my $count = count( \@hits, \@in_middle ) },
    hits_random    => sub { my $count = count( \@hits, \@random ) },
    control        => sub { my $count = count( [], [] ) },
  }
);
下面是各个部分是如何工作的。请注意,这是两个轴上的对数图,因此下陷线的坡度不像看上去那么接近:

因此,看起来smart match操作符有点智能,但这并不能真正帮助您,因为您可能仍然需要扫描整个阵列。您可能事先不知道在哪里可以找到您的元素。我期望散列将执行与最佳情况智能匹配相同的操作,即使您必须为此放弃一些内存


好的,所以智能匹配是智能乘以2是很好的,但真正的问题是“我应该使用它吗?”。另一种选择是散列查找,我一直担心我没有考虑过这种情况

与任何基准测试一样,在实际测试结果之前,我首先考虑结果可能是什么。我希望如果我已经有了散列,那么查找一个值的速度会非常快。那个案子不成问题。我更感兴趣的是我还没有大麻的情况。我能以多快的速度进行散列并查找密钥?我希望这场比赛的表现不是很好,但它还是比最坏情况下的聪明比赛好吗

但是,在看到基准之前,请记住,仅仅通过查看数字,几乎永远不会有足够的信息来说明应该使用哪种技术。问题的上下文选择最好的技术,而不是最快的、无上下文的微基准。考虑两种选择不同技术的情况:

  • 您有一个数组将重复搜索
  • 您总是会得到一个只需搜索一次的新数组
  • 您可以获得非常大的阵列,但内存有限
现在,记住这些,我在以前的程序中添加了:

my %old_hash = map {$_,1} @in_middle; 

cmpthese(-5, {
    ...,
    new_hash       => sub { 
        my %h = map {$_,1} @in_middle; 
        my $count = 0;
        foreach ( @hits ) { $count++ if exists $h{$_} }
        $count;
        },
    old_hash       => sub { 
        my $count = 0;
        foreach ( @hits ) { $count++ if exists $old_hash{$_} }
        $count;
        },
    control_hash   => sub { 
        my $count = 0;
        foreach ( @hits ) { $count++ }
        $count;
        },
    }
);
情节是这样的。颜色有点难以区分。最下面的一行是,您必须在任何时候创建散列来搜索它。那太糟糕了。最高的两行(绿色)是散列(实际上没有散列)和现有散列查找的控件。这是一个对数/对数图;这两种情况甚至比智能匹配控件(只调用一个子例程)都要快

还有几件事需要注意。“随机”情况下的行有点不同。这是可以理解的,因为每个基准测试(因此,每个阵列规模运行一次)都会随机将命中元素放置在候选阵列中。有些运行会将它们提前一点,有些则会推迟一点,但由于我在整个程序的每次运行中只生成一次
@random
数组,所以它们会移动一点。这意味着直线上的凹凸并不明显。如果我尝试所有位置并求平均值,我希望“随机”线与“中间”线相同

现在,看看这些结果,我想说智能匹配在最坏的情况下要比哈希查找在最坏的情况下快得多。这是有道理的。要创建一个散列,我必须访问数组的每个元素,还要创建散列,这需要大量的复制。智能匹配没有复制功能

这里有一个更进一步的案例,我不想研究。什么时候散列比智能匹配更好?也就是说,创建散列的开销何时会在重复搜索中充分分散,从而使散列成为更好的选择?

我相信“智能匹配”仍将继续
#!perl
use 5.12.2;
use strict;
use warnings;

use Benchmark qw(cmpthese);

my @hits = qw(A B C);
my @base = qw(one two three four five six) x ( $ARGV[0] || 1 );

my @at_end       = ( @base, @hits );
my @at_beginning = ( @hits, @base );

my @in_middle = @base;
splice @in_middle, int( @in_middle / 2 ), 0, @hits;

my @random = @base;
foreach my $item ( @hits ) {
    my $index = int rand @random;
    splice @random, $index, 0, $item;
    }

sub count {
    my( $hits, $candidates ) = @_;

    my $count;
    foreach ( @$hits ) { when( $candidates ) { $count++ } }
    $count;
    }

cmpthese(-5, {
    hits_beginning => sub { my $count = count( \@hits, \@at_beginning ) },
    hits_end       => sub { my $count = count( \@hits, \@at_end ) },
    hits_middle    => sub { my $count = count( \@hits, \@in_middle ) },
    hits_random    => sub { my $count = count( \@hits, \@random ) },
    control        => sub { my $count = count( [], [] ) },
  }
);
my %old_hash = map {$_,1} @in_middle; 

cmpthese(-5, {
    ...,
    new_hash       => sub { 
        my %h = map {$_,1} @in_middle; 
        my $count = 0;
        foreach ( @hits ) { $count++ if exists $h{$_} }
        $count;
        },
    old_hash       => sub { 
        my $count = 0;
        foreach ( @hits ) { $count++ if exists $old_hash{$_} }
        $count;
        },
    control_hash   => sub { 
        my $count = 0;
        foreach ( @hits ) { $count++ }
        $count;
        },
    }
);