Как я могу проверить, что значение присутствует в массиве (списке) в Perl? - PullRequest
20 голосов
/ 06 апреля 2009

У меня есть список возможных значений:

@a = qw(foo bar baz);

Как в сжатой форме проверить, присутствует или отсутствует значение $val в @a?

Очевидной реализацией является циклическое перемещение по списку, но я уверен TMTOWTDI .


Спасибо всем, кто ответил! Я хотел бы выделить три ответа:

  1. Принятый ответ - самый «встроенный» и обратно-совместимый способ.

  2. Ответ RET - самый чистый, но только для Perl 5.10 и новее.

  3. ответ draegtun (возможно) немного быстрее, но требует использования дополнительного модуля. Мне не нравится добавлять зависимости, если я могу их избежать, и в этом случае не нужна разница в производительности, но если у вас есть список из 1 000 000 элементов, вы можете попробовать этот ответ.

Ответы [ 8 ]

38 голосов
/ 06 апреля 2009

Если у вас есть Perl 5.10, используйте оператор умного совпадения ~~

print "Exist\n" if $var ~~ @array;

Это почти волшебство.

20 голосов
/ 06 апреля 2009

Функция Perl's bulit in grep () предназначена для этого.

@matches = grep( /^MyItem$/, @someArray ); 

или вы можете вставить любое выражение в совпадение

@matches = grep( $_ == $val, @a ); 
18 голосов
/ 06 апреля 2009

Ответ дан в ответе perlfaq4 на "Как я могу определить, содержится ли определенный элемент в списке или массиве?" .

Для поиска в perlfaq вы можете выполнить поиск по списку всех вопросов в perlfaq , используя ваш любимый браузер.

В командной строке вы можете использовать ключ -q для perldoc для поиска ключевых слов. Вы нашли бы свой ответ, выполнив поиск "list":

perldoc -q list

(части этого ответа предоставлены Анно Сигелем и Брайаном Д. Фой)

Слушание слова «in» указывает на то, что вам, вероятно, следовало использовать хеш, а не список или массив, для хранения ваших данных. Хэши предназначены для быстрого и эффективного ответа на этот вопрос. Массивы не являются.

При этом существует несколько способов приблизиться к этому. В Perl 5.10 и более поздних версиях вы можете использовать оператор интеллектуального сопоставления, чтобы проверить, содержится ли элемент в массиве или хэше:

use 5.010;

if( $item ~~ @array )
    {
    say "The array contains $item"
    }

if( $item ~~ %hash )
    {
    say "The hash contains $item"
    }

В более ранних версиях Perl вам придется проделать немного больше работы. Если вы собираетесь выполнить этот запрос много раз для произвольных строковых значений, возможно, самый быстрый способ - инвертировать исходный массив и сохранить хеш, ключи которого являются значениями первого массива:

@blues = qw/azure cerulean teal turquoise lapis-lazuli/;
%is_blue = ();
for (@blues) { $is_blue{$_} = 1 }

Теперь вы можете проверить, является ли $ is_blue {$ some_color}. Во-первых, было бы неплохо сохранить блюз в хэше.

Если все значения представляют собой маленькие целые числа, вы можете использовать простой индексированный массив. Этот вид массива займет меньше места:

@primes = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31);
@is_tiny_prime = ();
for (@primes) { $is_tiny_prime[$_] = 1 }
# or simply  @istiny_prime[@primes] = (1) x @primes;

Теперь вы проверяете, является ли $ is_tiny_prime [$ some_number].

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

@articles = ( 1..10, 150..2000, 2017 );
undef $read;
for (@articles) { vec($read,$_,1) = 1 }

Теперь проверьте, верно ли vec ($ read, $ n, 1) для некоторого $ n.

Эти методы гарантируют быстрые индивидуальные тесты, но требуют реорганизации исходного списка или массива. Они окупаются только в том случае, если вам нужно проверить несколько значений в одном массиве.

Если вы тестируете только один раз, стандартный модуль List :: Util сначала экспортирует функцию для этой цели. Он работает, останавливаясь, как только находит элемент. Он написан на C для скорости, и его Perl-эквивалент выглядит следующим образом:

sub first (&@) {
    my $code = shift;
    foreach (@_) {
        return $_ if &{$code}();
    }
    undef;
}

Если скорость не имеет большого значения, распространенная идиома использует grep в скалярном контексте (который возвращает количество элементов, прошедших его условие) для обхода всего списка. Тем не менее, вы можете узнать, сколько совпадений найдено.

my $is_there = grep $_ eq $whatever, @array;

Если вы хотите фактически извлечь соответствующие элементы, просто используйте grep в контексте списка.

my @matches = grep $_ eq $whatever, @array;
15 голосов
/ 06 апреля 2009

Используйте функцию first из List :: Util , которая входит в стандартную комплектацию Perl ....

use List::Util qw/first/;

my @a = qw(foo bar baz);
if ( first { $_ eq 'bar' } @a ) { say "Found bar!" }

NB. first возвращает первый найденный элемент, поэтому не нужно перебирать весь список (что и сделает grep ).

8 голосов
/ 06 апреля 2009

Один из возможных подходов - использовать функцию List :: MoreUtils 'any'.

use List::MoreUtils qw/any/;

my @array = qw(foo bar baz);

print "Exist\n" if any {($_ eq "foo")} @array;

Обновление : исправлено на основе комментария zoul.

5 голосов
/ 06 апреля 2009

Интересное решение, особенно для повторного поиска:

my %hash;
map { $hash{$_}++ } @a;
print $hash{$val};
2 голосов
/ 06 апреля 2009
$ perl -e '@a = qw(foo bar baz);$val="bar";
if (grep{$_ eq $val} @a) {
  print "found"
} else {
  print "not found"
}'

обнаруж

$val='baq';

не найдено

1 голос
/ 07 апреля 2009

Если вам не нравится ненужная зависимость, внедрите any или first себя

sub first (&@) {
  my $code = shift;
  $code->() and return $_ foreach @_;
  undef
}

sub any (&@) {
  my $code = shift;
  $code->() and return 1 foreach @_;
  undef
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...