我正在寻找列表中某个元素的存在。
在Python中有一个in
关键字,我会做类似的事情:
if element in list:
doTask
Perl中是否有等效的东西,而不必手动遍历整个列表?
Answers:
在v5.10.0中添加并在v5.10.1中进行了重大修改的智能匹配一直是人们经常抱怨的问题。尽管有很多方法可以使用它,但事实证明,它对Perl的用户和实现者都是有问题和令人困惑的。关于如何最好地解决该问题,已经有许多建议。显然,Smartmatch几乎肯定会在未来发生变化或消失。不建议依赖其当前行为。
现在,当解析器看到~~,给定或何时发出警告。
该智能匹配~~
操作。
if( $element ~~ @list ){ ... }
if( $element ~~ [ 1, 2, 3 ] ){ ... }
您也可以使用given
/when
构造。内部使用智能匹配功能。
given( $element ){
when( @list ){ ... }
}
您还可以将for
循环用作“ topicalizer”(表示设置$_
)。
for( @elements ){
when( @list ){ ... }
}
在Perl 5.12中将会出现的一件事是使用的修订后版本的能力when
。这使得它更像if
和unless
。
given( $element ){
... when @list;
}
您可能认为可以使用List :: Util :: first来摆脱困境,但是有一些边缘条件使它有问题。
在此示例中,很明显我们要成功匹配0
。不幸的是,此代码failure
每次都会打印。
use List::Util qw'first';
my $element = 0;
if( first { $element eq $_ } 0..9 ){
print "success\n";
} else {
print "failure\n";
}
您可以检查的返回值first
是否已定义,但是如果我们实际上希望匹配undef
成功,则将失败。
您可以放心使用grep
。
if( grep { $element eq $_ } 0..9 ){ ... }
这是安全的,因为grep
在标量上下文中被调用。在标量上下文中调用时,数组返回元素的数量。因此,即使我们尝试与匹配,这也将继续有效undef
。
您可以使用封闭for
循环。只要确保您致电last
,就可以在成功的比赛中退出循环。否则,您可能最终会多次运行代码。
for( @array ){
if( $element eq $_ ){
...
last;
}
}
您可以将for
循环放入if
语句的条件内...
if(
do{
my $match = 0;
for( @list ){
if( $element eq $_ ){
$match = 1;
last;
}
}
$match; # the return value of the do block
}
){
...
}
...但是将for
循环放在if
语句之前可能更清楚。
my $match = 0;
for( @list ){
if( $_ eq $element ){
$match = 1;
last;
}
}
if( $match ){ ... }
如果只匹配字符串,则还可以使用哈希。如果和@list
较大,这可以加快程序的速度,您将需要%hash
多次匹配。尤其是如果@array
它没有变化,那么您只需要加载%hash
一次即可。
my %hash = map { $_, 1 } @array;
if( $hash{ $element } ){ ... }
您也可以创建自己的子例程。这是使用原型有用的情况之一。
sub in(&@){
local $_;
my $code = shift;
for( @_ ){ # sets $_
if( $code->() ){
return 1;
}
}
return 0;
}
if( in { $element eq $_ } @list ){ ... }
~~
!它是如此波浪和有用!:)
#Smart-matching-in-detail
片段。
如果您打算多次执行此操作,则可以权衡查找时间的空间:
#!/usr/bin/perl
use strict; use warnings;
my @array = qw( one ten twenty one );
my %lookup = map { $_ => undef } @array;
for my $element ( qw( one two three ) ) {
if ( exists $lookup{ $element }) {
print "$element\n";
}
}
假定元素出现的次数@array
并不重要,并且其内容@array
是简单的标量。
$foo = first { ($_ && $_ eq "value" } @list; # first defined value in @list
或用于手摇类型:
my $is_in_list = 0;
foreach my $elem (@list) {
if ($elem && $elem eq $value_to_find) {
$is_in_list = 1;
last;
}
}
if ($is_in_list) {
...
稍有不同的版本可能会在很长的列表上更快:
my $is_in_list = 0;
for (my $i = 0; i < scalar(@list); ++$i) {
if ($list[i] && $list[i] eq $value_to_find) {
$is_in_list = 1;
last;
}
}
if ($is_in_list) {
...
在perl> = 5.10上,智能匹配运算符无疑是最简单的方法,正如许多其他人已经说过的那样。
在旧版本的perl上,我建议使用List :: MoreUtils :: any。
List::MoreUtils
不是核心模块(有人说应该是),但是它非常流行,并且包含在主要的perl发行版中。
具有以下优点:
in
一样),而不是返回元素的值List::Util::first
(就像上面提到的那样,这使得很难测试);grep
,它停在通过测试的第一个元素上(perl的智能匹配运算符也短路);这是一个适用于任何搜索(标量)值的示例,包括undef
:
use List::MoreUtils qw(any);
my $value = 'test'; # or any other scalar
my @array = (1, 2, undef, 'test', 5, 6);
no warnings 'uninitialized';
if ( any { $_ eq $value } @array ) {
print "$value present\n"
}
(在生产代码中,最好缩小的范围no warnings 'uninitialized'
)。
List::Util
似乎也有any
。
TIMTOWTDI
sub is (&@) {
my $test = shift;
$test->() and return 1 for @_;
0
}
sub in (@) {@_}
if( is {$_ eq "a"} in qw(d c b a) ) {
print "Welcome in perl!\n";
}
grep
在这里有帮助
if (grep { $_ eq $element } @list) {
....
}
List::Util::first
可能是一种更有效的方法。
List::Util::first
不是的替代品grep
。由于grep在标量上下文中被调用,因此您可以获得匹配元素的数量。如果List::Util::first
改用,则可能会检索一个元素,@list
该元素的比较结果为false ...
这篇博客文章讨论了这个问题的最佳答案。
简要来说,如果可以安装CPAN模块,那么最好的解决方案是:
if any(@ingredients) eq 'flour';
要么
if @ingredients->contains('flour');
但是,更常见的习惯用法是:
if @any { $_ eq 'flour' } @ingredients
我发现不太清楚。
但是请不要使用first()函数!它根本没有表达代码的意图。不要使用“智能匹配”运算符:它已损坏。而且不要使用带有哈希的grep()或解决方案:它们会遍历整个列表。虽然any()将在找到您的值后立即停止。
查看博客文章以了解更多详细信息。
PS:我正在为将来会遇到相同问题的人解答。
if any { $_ eq 'flour' } @ingredients
,但是您必须记住use List::MoreUtils qw/ any /;
。
如果您进行了一些自动加载黑客攻击,则可以在Perl中完成足够类似的语法。
创建一个小程序包来处理自动加载:
package Autoloader;
use strict;
use warnings;
our $AUTOLOAD;
sub AUTOLOAD {
my $self = shift;
my ($method) = (split(/::/, $AUTOLOAD))[-1];
die "Object does not contain method '$method'" if not ref $self->{$method} eq 'CODE';
goto &{$self->{$method}};
}
1;
然后,您的其他包或主脚本将包含一个子例程,该子例程返回受祝福的对象,当尝试调用其方法时,该对象将由Autoload处理。
sub element {
my $elem = shift;
my $sub = {
in => sub {
return if not $_[0];
# you could also implement this as any of the other suggested grep/first/any solutions already posted.
my %hash; @hash{@_} = ();
return (exists $hash{$elem}) ? 1 : ();
}
};
bless($sub, 'Autoloader');
}
这使您的用法看起来像:
doTask if element('something')->in(@array);
如果您重新组织闭包及其参数,则可以用另一种方式切换语法,使它看起来像这样,这与autobox样式有点接近:
doTask if search(@array)->contains('something');
执行此操作的功能:
sub search {
my @arr = @_;
my $sub = {
contains => sub {
my $elem = shift or return;
my %hash; @hash{@arr} = ();
return (exists $hash{$elem}) ? 1 : ();
}
};
bless($sub, 'Autoloader');
}