Как проверить, содержит ли массив Perl определенное значение?

Я пытаюсь выяснить способ проверки существования значения в массиве без итерации через массив.

Я читаю файл для параметра. У меня есть длинный список параметров, с которыми я не хочу иметь дело. Я разместил эти нежелательные параметры в массиве @badparams.

Я хочу прочитать новый параметр, и если он не существует в @badparams, обработайте его. Если он существует в @badparams, перейдите к следующему чтению.

Ответ 1

Просто превратите массив в хэш:

my %params = map { $_ => 1 } @badparams;

if(exists($params{$someparam})) { ... }

Вы также можете добавить в список дополнительные (уникальные) параметры:

$params{$newparam} = 1;

И позже получите список (уникальных) параметров назад:

@badparams = keys %params;

Ответ 2

Лучшее общее назначение - особенно короткие массивы (1000 единиц или менее) и кодеры, которые не уверены в том, какие оптимизации наилучшим образом соответствуют их потребностям.

# $value can be any regex. be safe
if ( grep( /^$value$/, @array ) ) {
  print "found it";
}

Было упомянуто, что grep проходит через все значения, даже если первое значение в массиве совпадает. Это верно, однако grep по-прежнему чрезвычайно быстрый для большинства случаев. Если вы говорите о коротких массивах (менее 1000 элементов), то большинство алгоритмов будет довольно быстрым в любом случае. Если вы говорите о очень длинном массиве (1 000 000 элементов), grep является приемлемо быстрым, независимо от того, является ли элемент первым или средним или последним в массиве.

Примеры оптимизации для более длинных массивов:

Если ваш массив отсортирован, используйте "двоичный поиск".

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

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

Примечание. Эти оптимизации будут только быстрее при работе с длинными массивами. Не превышайте оптимизацию.

Ответ 3

Вы можете использовать функцию smartmatch в Perl 5.10 следующим образом:

Для выполнения литерала, выполняемого ниже, будет выполняться трюк.

if ( "value" ~~ @array ) 

Для скалярного поиска выполнение ниже будет работать, как указано выше.

if ($val ~~ @array)

Для встроенного массива, сделанного ниже, будет работать, как указано выше.

if ( $var ~~ ['bar', 'value', 'foo'] ) 

В Perl 5.18 smartmatch помечен как экспериментальный, поэтому вам нужно отключить предупреждения, включив experimental прагму, добавив ниже к вашему script/module

use experimental 'smartmatch';

Альтернативно, если вы хотите избежать использования smartmatch - тогда, как сказал Аарон, используйте:

if ( grep( /^$value$/, @array ) ) {
  #TODO:
}

Ответ 4

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

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

any(@ingredients) eq 'flour';

или

@ingredients->contains('flour');

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

any { $_ eq 'flour' } @ingredients

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

any() остановится, как только он найдет ваше значение.

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

Ответ 5

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

#!/usr/bin/perl
use Benchmark;
my @list;
for (1..10_000) {
    push @list, $_;
}

timethese(10000, {
  'grep'    => sub {
            if ( grep(/^5000$/o, @list) ) {
                # code
            }
        },
  'hash'    => sub {
            my %params = map { $_ => 1 } @list;
            if ( exists($params{5000}) ) {
                # code
            }
        },
});

Выход контрольного теста:

Benchmark: timing 10000 iterations of grep, hash...
          grep:  8 wallclock secs ( 7.95 usr +  0.00 sys =  7.95 CPU) @ 1257.86/s (n=10000)
          hash: 50 wallclock secs (49.68 usr +  0.01 sys = 49.69 CPU) @ 201.25/s (n=10000)

Ответ 6

тест @eakssjo - мешает создавать хэши в цикле и создавать регулярные выражения в цикле. Фиксированная версия (плюс я добавил List::Util::first и List::MoreUtils::any):

use List::Util qw(first);
use List::MoreUtils qw(any);
use Benchmark;

my @list = ( 1..10_000 );
my $hit = 5_000;
my $hit_regex = qr/^$hit$/; # precompute regex
my %params;
$params{$_} = 1 for @list;  # precompute hash
timethese(
    100_000, {
        'any' => sub {
            die unless ( any { $hit_regex } @list );
        },
        'first' => sub {
            die unless ( first { $hit_regex } @list );
        },
        'grep' => sub {
            die unless ( grep { $hit_regex } @list );
        },
        'hash' => sub {
            die unless ( $params{$hit} );
        },
    });

И результат (это за 100_000 итераций, в десять раз больше, чем в ответе @eakssjo):

Benchmark: timing 100000 iterations of any, first, grep, hash...
       any:  0 wallclock secs ( 0.67 usr +  0.00 sys =  0.67 CPU) @ 149253.73/s (n=100000)
     first:  1 wallclock secs ( 0.63 usr +  0.01 sys =  0.64 CPU) @ 156250.00/s (n=100000)
      grep: 42 wallclock secs (41.95 usr +  0.08 sys = 42.03 CPU) @ 2379.25/s (n=100000)
      hash:  0 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU) @ 10000000.00/s (n=100000)
            (warning: too few iterations for a reliable count)

Ответ 7

Способ 1: grep (может быть осторожным, поскольку ожидается, что значение будет регулярным выражением).

Старайтесь не использовать grep, если смотреть на ресурсы.

if ( grep( /^$value$/, @badparams ) ) {
  print "found";
}

Метод 2: Линейный поиск

for (@badparams) {
    if ($_ eq $value) {
       print "found";
    }
}

Способ 3: используйте хэш

my %hash = map {$_ => 1} @badparams;
print "found" if (exists $hash{$value});

Способ 4: smartmatch

(добавлено в Perl 5.10, отмеченное экспериментально в Perl 5.18).

use experimental 'smartmatch';  # for perl 5.18
print "found" if ($value ~~ @badparams);

Способ 5: Использовать основной модуль List::MoreUtils

use List::MoreUtils qw(any uniq);;
@badparams = (1,2,3);
$value = 1;
print "found" if any {$_ eq $value} @badparams;

Ответ 8

Вы, конечно, хотите хэш здесь. Поместите плохие параметры в виде ключей в хеш, а затем определите, существует ли конкретный параметр в хеше.

our %bad_params = map { $_ => 1 } qw(badparam1 badparam2 badparam3)

if ($bad_params{$new_param}) {
  print "That is a bad parameter\n";
}

Если вам действительно интересно это сделать с массивом, посмотрите List::Util или List::MoreUtils

Ответ 9

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

my %bad_param_lookup;
@bad_param_lookup{ @bad_params } = ( 1 ) x @bad_params;

Но если это данные из символов слова и не слишком много мета, вы можете сбросить его в чередование регулярных выражений:

use English qw<$LIST_SEPARATOR>;

my $regex_str = do { 
    local $LIST_SEPARATOR = '|';
    "(?:@bad_params)";
 };

 # $front_delim and $back_delim being any characters that come before and after. 
 my $regex = qr/$front_delim$regex_str$back_delim/;

Это решение должно быть настроено для типов "плохих значений", которые вы ищете. И снова, это может быть совершенно неуместно для определенных типов строк, поэтому предостережение emptor.

Ответ 10

@files - это существующий массив

my @new_values =  grep(/^2[\d].[\d][A-za-z]?/,@files);

print join("\n", @new_values);

print "\n";

/^2[\d].[\d][A-za-z]?/= vaues, начиная с 2 здесь, вы можете поместить любое регулярное выражение

Ответ 11

my @badparams = (1,2,5,7,'a','zzz');

my $badparams = join('|',@badparams);   # '|' or any other character not present in params

foreach my $par (4,5,6,7,'a','z','zzz')
{
    if ($badparams =~ /\b$par\b/)
    {
        print "$par is present\n";
    }
    else
    {
        print "$par is not present\n";
    }
}

Возможно, вы захотите проверить согласованность конечных пробелов