Perl: if (элемент в списке)

60

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

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

if element in list:
    doTask

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

Теги:
arrays

10 ответов

94
Лучший ответ

UPDATE:

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

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

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




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

  • Оператор 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;
}

Если вам нужно работать в старых версиях 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 } ){ ... }
    
  • Вы также можете создать свою собственную подпрограмму. Это один из случаев, когда полезно использовать prototypes.

    sub in(&@){
      local $_;
      my $code = shift;
      for( @_ ){ # sets $_
        if( $code->() ){
          return 1;
        }
      }
      return 0;
    }
    
    if( in { $element eq $_ } @list ){ ... }
    
  • 0
    очень хорошо, хотя и слишком длинный ответ
  • 0
    @BradGilbert Хорошая работа!
Показать ещё 4 комментария
15
if( $element ~~ @list ){
   do_task
}

~~ является "оператором интеллектуального соответствия" и не только распознает членство в списке.

  • 7
    Это ново для 5.10, верно?
  • 3
    Ты прав.
Показать ещё 1 комментарий
9

Список:: Util:: first

$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) {
   ...
  • 0
    это довольно наполовину. -1
8

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

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

  • 1
    Хорошая техника, о которой стоит упомянуть.
  • 0
    хорошая техника с упоминанием о том, что она окупается только при многократном поиске. +1
6

Список:: MoreUtils

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

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

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

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

  • возвращает true/false (как это делает Python in), а не значение элемента, поскольку List::Util::first делает (что затрудняет тестирование, как указано выше);
  • в отличие от grep, он останавливается на первом элементе, который проходит тест (также как и короткие замыкания оператора perl smart match);
  • он работает с любой версией 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"
}

P.S.

(В производственном коде лучше сузить область no warnings 'uninitialized').

6

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";
}
  • 1
    Мне это нравится !
  • 0
    Мне это не нравится, я ненавижу Perl.
5

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

if (grep { $_ eq $element } @list) {
    ....
}
  • 6
    List::Util::first , вероятно, более эффективный способ сделать это.
  • 0
    Но есть несколько способов ...
Показать ещё 4 комментария
3

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

use Perl6::Junction qw/ any /;

if (any(@grant) eq 'su') {
    ...
}
1

Вы можете выполнить аналогичный синтаксис в 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');
}
1

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

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

if any(@ingredients) eq 'flour';

или

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

Однако более обычная идиома:

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

который я считаю менее понятным.

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

Подробнее читайте в блоге.

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

  • 1
    Я думаю, что обычная идиома, if any { $_ eq 'flour' } @ingredients , но вы должны помнить, чтобы use List::MoreUtils qw/ any /; ,

Ещё вопросы

Сообщество Overcoder
Наверх
Меню