Я ищу присутствие элемента в списке.
В Python есть ключевое слово in
, и я бы сделал что-то вроде:
if element in list:
doTask
Есть ли что-то эквивалентное в Perl, без необходимости вручную перебирать весь список?
Я ищу присутствие элемента в списке.
В Python есть ключевое слово in
, и я бы сделал что-то вроде:
if element in list:
doTask
Есть ли что-то эквивалентное в Perl, без необходимости вручную перебирать весь список?
Семейство функций smartmatch теперь является экспериментальным
Интеллектуальное совпадение, добавленное в v5.10.0 и значительно переработанное в версии 5.10.1, является регулярной жалобой. Хотя есть несколько способов, в которых это полезно, он также оказался проблематичным и запутанным как для пользователей, так и для разработчиков Perl. Был предложен ряд предложений о том, как наилучшим образом решить эту проблему. Понятно, что smartmatch почти наверняка либо изменится, либо уйдет в будущем. Не рекомендуется полагаться на его текущее поведение.
Теперь предупреждения будут выдаваться, когда парсер увидит ~~, данный или когда.
Оператор smart match ~~
.
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 } ){ ... }
Вы также можете создать свою собственную подпрограмму. Это один из случаев, когда полезно использовать prototypes.
sub in(&@){
local $_;
my $code = shift;
for( @_ ){ # sets $_
if( $code->() ){
return 1;
}
}
return 0;
}
if( in { $element eq $_ } @list ){ ... }
if( $element ~~ @list ){
do_task
}
~~
является "оператором интеллектуального соответствия" и не только распознает членство в списке.
$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) {
...
Если вы планируете делать это много раз, вы можете обменять пространство для времени поиска:
#!/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
- простые скаляры.
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";
}
В perl >= 5.10 оператор smart match, безусловно, самый простой способ, как уже говорили многие другие.
В старых версиях perl я бы предложил List:: MoreUtils:: any.
List::MoreUtils
не является основным модулем (некоторые говорят, что он должен быть), но он очень популярен и включен в основные дистрибутивы perl.
Он имеет следующие преимущества:
in
), а не значение элемента, поскольку List::Util::first
делает (что затрудняет тестирование, как указано выше);grep
, он останавливается на первом элементе, который проходит тест (также как и короткие замыкания оператора perl smart match);Вот пример, который работает с любым поисковым (скалярным) значением, включая 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'
).
grep
здесь полезен
if (grep { $_ eq $element } @list) {
....
}
Вероятно, Perl6::Junction
- самый ясный способ сделать. Никаких зависимостей XS, беспорядка и новой версии perl не требуется.
use Perl6::Junction qw/ any /;
if (any(@grant) eq 'su') {
...
}
В этом сообщении в блоге обсуждаются лучшие ответы на этот вопрос.
В качестве краткого резюме, если вы можете установить модули CPAN, лучшие решения:
if any(@ingredients) eq 'flour';
или
if @ingredients->contains('flour');
Однако более обычная идиома:
if @any { $_ eq 'flour' } @ingredients
который я считаю менее понятным.
Но, пожалуйста, не используйте функцию first()! Он не отражает намерения вашего кода вообще. Не используйте оператор "Smart match": он сломан. И не используйте grep() или решение с хешем: они перебирают весь список. Хотя any() остановится, как только он найдет ваше значение.
Подробнее читайте в блоге.
PS: Я отвечаю за людей, у которых будет тот же вопрос в будущем.
Вы можете выполнить аналогичный синтаксис в Perl, если вы выполните Autoload взлом.
Создайте небольшой пакет для обработки автозагрузки:
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;
Тогда ваш другой пакет или основной script будет содержать подпрограмму, которая возвращает объект, который обрабатывается 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');
}