Как я могу проверить, что значение присутствует в массиве (списке) в Perl?

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

@a = qw(foo bar baz);

Как быстро проверить наличие или отсутствие значения $val в @a?

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


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

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

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

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


person MaxVT    schedule 06.04.2009    source источник
comment
Я не уверен, что вижу проблему зависимости с List::Util. Это стандартно для Perl, и если вы используете его с qw/first/ (как это сделал Драгтун), вы импортируете только одну подпрограмму.   -  person Telemachus    schedule 06.04.2009
comment
Это не проблема как таковая, это скорее личные предпочтения.   -  person MaxVT    schedule 06.04.2009
comment
В ответе List::Util нет проблем с зависимостями. Если бы это был я, это был бы принятый ответ. Нежелание использовать основные модули кажется мне предпочтением, основанным на суеверии. В этом случае grep {} почти так же хорош.   -  person singingfish    schedule 07.04.2009


Ответы (8)


Встроенная в Perl функция grep() предназначена для этого.

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

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

@matches = grep( $_ == $val, @a ); 
person Cheekysoft    schedule 06.04.2009


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

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

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

perldoc -q list

(части этого ответа предоставлены Anno Siegel и brian d foy)

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

При этом есть несколько подходов к этому. В 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;
person brian d foy    schedule 06.04.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).

person draegtun    schedule 06.04.2009
comment
за глупого брата, при использовании импортированного подпрограммы я бы предпочел List::MoreUtil::any(), потому что концепция (возвращает истинное значение, если какой-либо элемент в LIST соответствует критерию) семантически лучше соответствует вопросу, чем first() (возвращает первый элемент, где результат BLOCK является истинным значением.) - person nohat; 07.04.2009
comment
Преимущество List::Util::first() состоит в том, что он является основным модулем (т. е. вездесущим). Если бы я искал варианты CPAN, я бы серьезно рассмотрел Perl6::Junction::any... if ( any(@a) eq 'baz' ) {} - person draegtun; 07.04.2009

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

use List::MoreUtils qw/any/;

my @array = qw(foo bar baz);

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

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

person neversaint    schedule 06.04.2009
comment
Определено (foo) всегда верно, вы имели в виду $_ eq 'foo'? - person zoul; 06.04.2009
comment
Я продолжаю снимать -1, которую я тебе дал, и он продолжает надевать его обратно... Надеюсь, в этот (3-й) раз он не исчезнет! - person j_random_hacker; 08.04.2009

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

my %hash;
map { $hash{$_}++ } @a;
print $hash{$val};
person zoul    schedule 06.04.2009
comment
Предложение Зула о хэше довольно близко к оптимальному, однако я бы посоветовал, когда вы добавляете и удаляете значения в свой массив в ходе своей программы, добавляйте и удаляйте эти значения в своем хэше. - person Robert S. Barnes; 06.04.2009
comment
Кроме того, хотя это работает и распространено, некоторые люди (включая меня, я думаю) будут жаловаться на использование карты в пустом контексте. Почему бы не использовать $hash{$_}++ вместо @a? - person jettero; 06.04.2009

$ perl -e '@a = qw(foo bar baz);$val="bar";
if (grep{$_ eq $val} @a) {
  print "found"
} else {
  print "not found"
}'

нашел

$val='baq';

не найден

person mfontani    schedule 06.04.2009

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

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

sub any (&@) {
  my $code = shift;
  $code->() and return 1 foreach @_;
  undef
}
person Hynek -Pichi- Vychodil    schedule 07.04.2009