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

211

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

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

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

  • 3
    Для протокола, ответ зависит от вашей ситуации. Звучит так, как будто вы хотите сделать повторный поиск, поэтому использование хэша, как подсказывает jkramer, хорошо. Если вы хотите сделать только один поиск, вы можете просто повторить. (И в некоторых случаях вы можете захотеть выполнить бинарный поиск вместо использования хеша!)
  • 5
    perldoc -f grep
Показать ещё 1 комментарий
Теги:
arrays
comparison

11 ответов

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

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

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

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

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

$params{$newparam} = 1;

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

@badparams = keys %params;
  • 34
    Для записи, этот код по-прежнему перебирает массив. Вызов map {} просто упрощает ввод этой итерации.
  • 3
    Я сделал бы это только в том случае, если ваши значения в @badparams псевдостатичны, и вы планируете много проверять карту. Я не рекомендовал бы это для единственной проверки.
Показать ещё 2 комментария
186

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

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

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

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

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

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

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

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

  • 0
    Я считаю это более читабельным, чем метод хэширования. Казалось бы, единственный раз, когда метод хеширования имеет смысл, - это когда нужно проверить несколько значений.
  • 25
    if ("value" ~~ @array) правильный ответ
Показать ещё 6 комментариев
102

Вы можете использовать функцию 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
    Это хорошо, но, кажется, плохо знакомо с Perl 5.10. Прошло некоторое время, прежде чем я понял, почему я получаю синтаксические ошибки.
  • 12
    Предупреждение: вы можете избежать этого, так как в разных версиях оператор, по-видимому, ведет себя по-разному и тем временем был помечен как экспериментальный . Поэтому, если у вас нет полного контроля над вашей версией perl (и у кого она есть), вам, вероятно, следует избегать ее.
Показать ещё 2 комментария
38

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

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

any(@ingredients) eq 'flour';

или

@ingredients->contains('flour');

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

any { $_ eq 'flour' } @ingredients

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

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

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

11

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

#!/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)
  • 4
    Использование List::Util::first быстрее, так как оно прекращает итерацию, когда находит совпадение.
  • 1
    -1 Ваш бенчмарк имеет дефекты, grep значительно медленнее, чем создание хэша и поиск, так как первым является O (n), а последним O (1). Просто сделайте создание хеша только один раз (вне цикла) и предварительно вычислите регулярное выражение только для измерения методов ( см. Мой ответ ).
Показать ещё 4 комментария
10

тест @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)
  • 6
    Если вы хотите проверить несколько элементов, то предварительное создание хеша экономит ваше время. Но если вы просто хотите узнать, содержит ли он один элемент, то у вас уже нет хэша. Поэтому создание хэша должно быть частью вычислительного времени. Тем более для регулярного выражения: вам нужно новое регулярное выражение для каждого элемента, который вы ищете.
  • 1
    @fishinear Верно, но если вас интересует только одна проверка, а не несколько проверок, то очевидно, что микрооптимизация даже не в том, чтобы узнать, какой метод быстрее, потому что эти микросекунды не имеют значения. Если вы хотите повторить эту проверку, лучше использовать хеш, потому что стоимость создания хеша достаточно мала, чтобы ее можно было игнорировать. Выше тестов измеряются только различные способы тестирования, не включая какие-либо настройки. Да, это может быть недействительным в вашем случае использования, но опять же - если вы делаете только одну проверку, вы должны использовать то, что наиболее читабельно для вас и ваших товарищей.
3

Способ 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;
2

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

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

0

@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 здесь, вы можете поместить любое регулярное выражение

0
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";
    }
}

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

0

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

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.

  • 1
    Вы также можете написать @bad_param_lookup{@bad_params} = () , но вам нужно будет использовать exists для проверки членства.

Ещё вопросы

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