此功能与exists
与哈希相同。
我打算多久使用它。
可以通过某种方式进行优化吗?
my @a = qw/a b c d/;
my $ret = array_exists("b", @a);
sub array_exists {
my ($var, @a) = @_;
foreach my $e (@a) {
if ($var eq $e) {
return 1;
}
}
return 0;
}
答案 0 :(得分:12)
如果必须在固定数组上执行此操作,请改为使用哈希:
my %hash = map { $_, 1 } @array;
if( exists $hash{$key} ) { ... }
有些人会选择智能匹配运算符,但这是我们需要从Perl中删除的功能之一。您需要确定它是否应匹配,其中数组包含具有键b
的哈希引用的数组引用:
use 5.010;
my @a = (
qw(x y z),
[ { 'b' => 1 } ],
);
say 'Matches' if "b" ~~ @a; # This matches
由于智能匹配是递归的,因此如果继续进入数据结构。我在Rethinking smart matching中写了一些内容。
答案 1 :(得分:8)
答案 2 :(得分:7)
我会使用List::MoreUtils::any。
my $ret = any { $_ eq 'b' } @a;
答案 3 :(得分:4)
由于StackOverflow上有很多类似的问题,其中有不同的正确答案"返回不同的结果,我试着比较它们。这个问题似乎是分享我的小基准的好地方。
对于我的测试,我使用了长度为10的1,000个元素(字符串)的测试集(@test_set
),其中只有一个元素($search_value
)与给定的字符串匹配。
我采用以下语句来验证这个元素在100,000转的循环中是否存在。
<强> _grep 强>
grep( $_ eq $search_value, @test_set )
<强> _hash 强>
{ map { $_ => 1 } @test_set }->{ $search_value }
<强> _hash_premapped 强>
$mapping->{ $search_value }
$mapping
的{{1}}(包含在最终测量中)_ <强>正则表达式强>
$mapping = { map { $_ => 1 } @test_set }
_ <强> regex_prejoined 强>
sub{ my $rx = join "|", map quotemeta, @test_set; $search_value =~ /^(?:$rx)$/ }
$search_value =~ /^(?:$rx)$/
的正则表达式$rx
(包含在最终测量中)<强> _manual_first 强>
$rx = join "|", map quotemeta, @test_set;
<强> _First 强>
sub{ foreach ( @test_set ) { return 1 if( $_ eq $search_value ); } return 0; }
first { $_ eq $search_value } @test_set
(版本1.38)<强> _smart 强>
List::Util
<强> _any 强>
$search_value ~~ @test_set
any { $_ eq $search_value } @test_set
(版本0.33)在我的机器上(Ubuntu,3.2.0-60-generic,x86_64,Perl v5.14.2)我得到了以下结果。显示的值为秒,由List::MoreUtils
和gettimeofday
tv_interval
(版本1.9726)返回。
元素Time::HiRes
位于数组$search_value
@test_set
元素_hash_premapped: 0.056211
_smart: 0.060267
_manual_first: 0.064195
_first: 0.258953
_any: 0.292959
_regex_prejoined: 0.350076
_grep: 5.748364
_regex: 29.27262
_hash: 45.638838
位于数组$search_value
@test_set
元素_hash_premapped: 0.056316
_regex_prejoined: 0.357595
_first: 2.337911
_smart: 2.80226
_manual_first: 3.34348
_any: 3.408409
_grep: 5.772233
_regex: 28.668455
_hash: 45.076083
位于数组$search_value
@test_set
<强>结论强>
检查数组中元素是否存在的最快方法是使用准备好的哈希。你当然通过一定比例的内存消耗购买它,只有你多次搜索集合中的元素才有意义。如果您的任务包含少量数据且只进行一次或几次搜索,则哈希甚至可能是最差的解决方案。快速的方式不一样,但类似的想法是使用准备好的正则表达式,这似乎有更短的准备时间。
在许多情况下,准备好的环境是不可取的。
令人惊讶的是_hash_premapped: 0.054434
_regex_prejoined: 0.362615
_first: 4.383842
_smart: 5.536873
_grep: 5.962746
_any: 6.31152
_manual_first: 6.59063
_regex: 28.695459
_hash: 45.804386
在语句比较方面有非常好的结果,没有准备好的环境。虽然搜索值在开头(也可能被解释为较小集合中的结果),但它非常接近收藏夹List::Util::first
和~~
(甚至可能在测量不准确)。对于较大测试集中间或末尾的项目,第一肯定是最快的。
答案 4 :(得分:3)
brian d foy建议使用散列,这会产生O(1)查找,代价是稍微更昂贵的散列创建。 Marc Jason Dominus在他的书“高阶Perl”中描述了一种技术,其中使用散列来记忆(或缓存)给定参数的子结果。因此,例如,如果findit(1000)
始终为给定参数返回相同的内容,则无需每次都重新计算结果。该技术在Memoize模块(Perl核心的一部分)中实现。
记忆并不总是一场胜利。有时,memoized包装器的开销大于计算结果的开销。有时,给定参数不可能被多次检查或相对较少次检查。有时,无法保证给定参数的函数结果总是相同(即缓存可能变得陈旧)。但是如果你有一个昂贵的函数,每个参数的返回值都很稳定,那么memoization就是一个很大的胜利。
就像brian d foy的回答使用哈希一样,Memoize在内部使用哈希。 Memoize实现中还有额外的开销,但使用Memoize的好处是它不需要重构原始子例程。您只需use Memoize;
然后memoize( 'expensive_function' );
,前提是它符合从备忘录中受益的条件。
我使用了原始子程序并将其转换为整数(仅为了简化测试)。然后我添加了第二个版本,它传递了对原始数组的引用,而不是复制数组。有了这两个版本,我创建了两个我记忆的潜艇。然后我对四个潜艇进行了基准测试。
在基准测试中,我不得不做出一些决定。首先,要测试多少次迭代。我们测试的迭代次数越多,我们就越有可能为memoized版本提供良好的缓存命中率。然后我还必须决定将多少项放入样本数组中。项目越多,缓存命中的可能性就越小,但缓存命中发生时的节省越多。我最终决定要搜索包含8000个元素的数组,并选择搜索24000次迭代。这意味着平均每个memoized调用应该有两个缓存命中。 (使用给定参数的第一次调用将写入缓存,而第二次和第三次调用将从缓存中读取,因此平均有两次良好的命中。)
这是测试代码:
use warnings;
use strict;
use Memoize;
use Benchmark qw/cmpthese/;
my $n = 8000; # Elements in target array
my $count = 24000; # Test iterations.
my @a = ( 1 .. $n );
my @find = map { int(rand($n)) } 0 .. $count;
my ( $orx, $ormx, $opx, $opmx ) = ( 0, 0, 0, 0 );
memoize( 'orig_memo' );
memoize( 'opt_memo' );
cmpthese( $count, {
original => sub{ my $ret = original( $find[ $orx++ ], @a ); },
orig_memo => sub{ my $ret = orig_memo( $find[ $ormx++ ], @a ); },
optimized => sub{ my $ret = optimized( $find[ $opx++ ], \@a ); },
opt_memo => sub{ my $ret = opt_memo( $find[ $opmx++ ], \@a ); }
} );
sub original {
my ( $var, @a) = @_;
foreach my $e ( @a ) {
return 1 if $var == $e;
}
return 0;
}
sub orig_memo {
my ( $var, @a ) = @_;
foreach my $e ( @a ) {
return 1 if $var == $e;
}
return 0;
}
sub optimized {
my( $var, $aref ) = @_;
foreach my $e ( @{$aref} ) {
return 1 if $var == $e;
}
return 0;
}
sub opt_memo {
my( $var, $aref ) = @_;
foreach my $e ( @{$aref} ) {
return 1 if $var == $e;
}
return 0;
}
以下是结果:
Rate orig_memo original optimized opt_memo
orig_memo 876/s -- -10% -83% -94%
original 972/s 11% -- -82% -94%
optimized 5298/s 505% 445% -- -66%
opt_memo 15385/s 1657% 1483% 190% --
正如您所看到的,原始函数的memoized版本实际上更慢。这是因为原始子程序的大部分成本花费在制作8000元素数组的副本上,再加上memoized版本还有额外的调用堆栈和簿记开销。
但是一旦我们传递一个数组引用而不是一个副本,我们就消除了传递整个数组的费用。你的速度大幅提升。但明显的赢家是我们记忆(缓存)的优化(即传递数组引用)版本,比原始函数快1483%。通过memoization,O(n)查找仅在第一次检查给定参数时发生。后续查找在O(1)时间内进行。
现在你必须决定(通过Benchmarking)memoization是否对你有所帮助。当然传递一个数组引用。如果memoization对你没有帮助,也许brian的哈希方法是最好的。但是,在不必重写大量代码方面,memoization与传递数组ref相结合可能是一个很好的选择。
答案 5 :(得分:2)
您当前的解决方案会在找到要查找的元素之前遍历数组。因此,它是线性算法。
如果您首先使用关系运算符(>
表示数字元素,gt
表示字符串)对数组进行排序,则可以使用binary search来查找元素。它是对数算法,比线性快得多。
当然,首先必须考虑对数组进行排序的惩罚,这是一个相当慢的操作( n log n )。如果您要匹配的数组的内容经常更改,则必须在每次更改后进行排序,并且它变得非常慢。如果在您最初对它们进行排序后内容保持不变,则二进制搜索最终会实际上更快。
答案 6 :(得分:2)
您可以使用grep:
sub array_exists {
my $val = shift;
return grep { $val eq $_ } @_;
}
令人惊讶的是,它与List :: MoreUtils'any()
的速度并没有太大差距。如果您的商品位于列表的最后,如果您的商品位于列表的开头,则该商品在列表末尾的速度提高约25%,速度提高约50%。
如果需要,您也可以内联它 - 无需将其推送到子程序中。即。
if ( grep { $needle eq $_ } @haystack ) {
### Do something
...
}