2009-04-06 2 views
20

У меня есть список возможных значений:Как проверить, что значение присутствует в массиве (списке) в Perl?

@a = qw(foo bar baz); 

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

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


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

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

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

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

+1

я не уверен, что я вижу проблемы зависимостей с List :: Util. Он стандартный с Perl, и если вы используете его с qw/first/(как это делал Draegtun), вы импортируете только одну подпрограмму. – Telemachus

+0

Это не проблема сама по себе, это скорее личное предпочтение. – MaxVT

+1

Проблема с зависимостью связана с ответом List :: Util. Если бы это был я, это был бы принятый ответ. Нежелание использовать основные модули поражает меня как предпочтение, основанное на суевериях. В этом случае grep {} почти так же хорош. – singingfish

ответ

20

Perl в Grep() функция предназначена, чтобы сделать это.

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

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

@matches = grep($_ == $val, @a); 
+0

+1 Эй! Простой и приятный =) Спасибо – Viet

8

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

use List::MoreUtils qw/any/; 

my @array = qw(foo bar baz); 

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

Update: корректируют на основании комментария Zoul в.

+0

Определено («foo») всегда верно, вы имели в виду $ _ eq 'foo'? – zoul

+0

-1 извините, за что назвал зол. –

+0

Да, это должно быть любое {$ _ eq "foo"} или любое {m/^ foo \ z /} ... – jettero

5

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

my %hash; 
map { $hash{$_}++ } @a; 
print $hash{$val}; 
+0

Предложение Zoul о хэше довольно близко к оптимальному, но я бы предположил, что по мере добавления и удаления значений в ваш массив во время вашей программы добавьте и удалите эти значения в своем хэше. –

+0

Кроме того, хотя это работает и распространено, некоторые люди (включая меня, я полагаю) будут жаловаться на использование карты в контексте пустоты. Почему вместо $ hash {$ _} ++ для @a вместо этого? – jettero

+0

Да, это лучше. – zoul

38

Если у вас есть Perl 5.10, используйте smart-match operator ~~

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

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

+0

NB. Из 5.10.1 семантика немного изменилась, и это должно быть: 'if $ var ~~ @ array'. Чтобы я мог подумать о '~~' как 'in'. ref: http://perldoc.perl.org/perldelta.html#Smart-match-changes – draegtun

+0

Спасибо - заменили заказ соответствующим образом. – RET

+1

My perldoc url уже недействителен. Здесь исправлено одно: http://search.cpan.org/~dapm/perl-5.10.1/pod/perl5101delta.pod#Smart_match_changes – draegtun

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

найдено

$val='baq'; 

не найден

15

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

use List::Util qw/first/; 

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

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

+1

за каждый foolishbrat, если использовать импортированный элемент, я бы предпочел List :: MoreUtil :: any(), потому что концепция («Возвращает истинное значение, если какой-либо элемент в LIST соответствует критерию») семантически лучше подходит к вопросу, чем first() («возвращает первый элемент, где результат из BLOCK является истинным значением. ") – nohat

+0

Список :: Util :: first() имеет то преимущество, что является основным модулем (т. Е. Вездесущим). Если бы я искал варианты CPAN, я бы серьезно подумал о Perl6 :: Junction :: any ... if (any (@a) eq 'baz') {} – draegtun

18

Ответ получен в ответ perlfaq4 на номер "How can I tell whether a certain element is contained in a list or array?".

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

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

perldoc -q list 

(части этого ответа, вносимого Anno Siegel и Брайан d Фой)

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

Это, как говорится, есть несколько способов приблизиться к этому. В 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 ($ чтения, $ п, 1) является true для некоторого $ 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; 
1

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

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

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