Perl: if (элемент в списке) - PullRequest
68 голосов
/ 05 марта 2010

Я ищу наличие элемента в списке.

В Python есть ключевое слово in, и я бы сделал что-то вроде:

if element in list:
    doTask

Есть ли что-то эквивалентное в Perl без необходимости вручную перебирать весь список?

Ответы [ 10 ]

109 голосов
/ 05 марта 2010

UPDATE:

Семейство функций Smartmatch теперь является экспериментальным

Интеллектуальное совпадение, добавленное в v5.10.0 и значительно пересмотренное в v5.10.1, было обычной жалобой. Хотя существует несколько способов его использования, он также оказался проблематичным и запутанным как для пользователей, так и для разработчиков Perl. Был целый ряд предложений о том, как наилучшим образом решить проблему. Понятно, что smartmatch почти наверняка либо изменится, либо исчезнет в будущем. Полагаться на его текущее поведение не рекомендуется.

Предупреждения теперь будут выдаваться, когда синтаксический анализатор видит ~~, данное или когда.




Если вам не нужен Perl v5.10, вы можете использовать любой из следующих примеров.

  • Оператор smart match ~~.

    if( $element ~~ @list ){ ... }
    if( $element ~~ [ 1, 2, 3 ] ){ ... }
    
  • Вы также можете использовать конструкцию given / when. Который использует функциональность интеллектуального соответствия внутри.

    given( $element ){
       when( @list ){ ... }
    }
    
  • Вы также можете использовать цикл for как «актуализатор» (то есть он устанавливает $_).

    for( @elements ){
       when( @list ){ ... }
    }
    

Одна вещь, которая появится в Perl 5.12, - это возможность использовать версию when после исправления. Что делает его еще более похожим на if и unless.

given( $element ){
  ... when @list;
}

Если вам нужно работать на более старых версиях Perl, есть еще несколько вариантов.

  • Вы можете подумать, что можете обойтись без использования 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 ){ ... }
    
15 голосов
/ 05 марта 2010
if( $element ~~ @list ){
   do_task
}

~~ является «оператором интеллектуального сопоставления» и выполняет больше, чем просто обнаружение членства в списке.

9 голосов
/ 05 марта 2010

Список :: Util :: первый

$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) {
   ...
8 голосов
/ 05 марта 2010

Если вы планируете делать это много раз, вы можете обменять пространство на время поиска:

#!/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 - простые скаляры.

7 голосов
/ 24 ноября 2011

Список :: MoreUtils

На perl> = 5.10 оператор умного совпадения, безусловно, самый простой способ, как уже говорили многие другие.

В старых версиях perl я бы предложил List :: MoreUtils :: any .

List::MoreUtils не является основным модулем (некоторые говорят, что так и должно быть), но он очень популярен и включен в основные дистрибутивы Perl.

Имеет следующие преимущества:

  • возвращает true / false (как это делает in в Python), а не значение элемента, как List::Util::first (что затрудняет проверку, как отмечено выше);
  • в отличие от grep, он останавливается на первом элементе, который проходит тест (также оператор умного соответствия perl короткое замыкание );
  • работает с любой версией perl (ну, по крайней мере,> = 5.00503).

Вот пример, который работает с любым искомым (скалярным) значением, включая 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').

6 голосов
/ 05 марта 2010

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";
}
5 голосов
/ 05 марта 2010

grep здесь полезно

if (grep { $_ eq $element } @list) {
    ....
}
3 голосов
/ 24 августа 2016

Вероятно, Perl6::Junction - самый ясный способ сделать это. Нет зависимостей XS, нет путаницы и не требуется новая версия Perl.

use Perl6::Junction qw/ any /;

if (any(@grant) eq 'su') {
    ...
}
2 голосов
/ 27 января 2017

Вы можете выполнить достаточно похожий синтаксис в 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');
}
2 голосов
/ 22 мая 2013

В этом блоге обсуждаются лучшие ответы на этот вопрос.

Вкратце, если вы можете установить модули CPAN, то лучшие решения:

if any(@ingredients) eq 'flour';

или

if @ingredients->contains('flour');

Тем не менее, более обычная идиома:

if @any { $_ eq 'flour' } @ingredients

, которую я считаю менее ясной.

Но, пожалуйста, не используйте первую () функция!Он не выражает цели вашего кода вообще.Не используйте оператор «Smart match»: он сломан.И не используйте grep () и решение с хешем: они перебирают весь список.Хотя any () прекратит работу, как только найдет вашу ценность.

Для получения более подробной информации просмотрите сообщение в блоге.

PS: я отвечаю за людей, у которых будет такой же вопрос вбудущее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...