Як я можу перевірити, чи містить масив Perl певне значення?


239

Я намагаюся з'ясувати спосіб перевірки наявності значення в масиві без ітерації через масив.

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

Я хочу прочитати новий параметр, і якщо його немає в @badparams, обробіть його. Якщо він існує @badparams, перейдіть до наступного читання.


3
Для запису відповідь залежить від вашої ситуації. Це здається, що ви хочете робити повторні пошуки, тому використання хешу, як пропонує jkramer, добре. Якщо ви хотіли зробити один пошук, ви можете просто повторити. (І в деяких випадках ви можете скористатися двійковим пошуком, а не використовувати хеш!)
Cascabel


6
Для запису (а це може бути абсолютно непридатним у вашій ситуації), як правило, краща ідея визначити "хороші цінності" та ігнорувати решту, а не намагатися вилучити відомі "погані значення". Питання, яке вам потрібно задати - це можливо, що можуть бути деякі погані значення, про які ви ще не знаєте.
Грант Маклін

Відповіді:


187

Просто перетворіть масив у хеш:

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

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

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

$params{$newparam} = 1;

А пізніше поверніть список (унікальних) парамів:

@badparams = keys %params;

38
Для запису цей код все ще повторюється через масив. Виклик на карті {} просто робить цю ітерацію дуже простою.
Кенні Віланд

3
Я б це зробив, лише якщо ваші значення в @badparams є псевдостатичними і ви плануєте зробити багато перевірок на карті. Я б не рекомендував цього за одну перевірку.
Аарон Т Харріс

Чи не буде це піти на масив з кількома елементами з однаковим значенням?
Роб Уеллс

3
@RobWells ні, це буде добре працювати. Наступного разу, коли воно побачить те саме значення, воно просто перезаписує запис у хеші, що в цьому випадку встановлює його 1знову.
andrewrjones

222

Найкраще загальне призначення - особливо короткі масиви (1000 предметів або менше) та кодери, які не знають, які оптимізації найкраще відповідають їх потребам.

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

Згадано, що grep проходить через усі значення, навіть якщо перше значення в масиві відповідає. Це правда, однак греп все ще надзвичайно швидкий у більшості випадків . Якщо ви говорите про короткі масиви (менше 1000 елементів), то в більшості випадків більшість алгоритмів будуть досить швидкими. Якщо ви говорите про дуже довгі масиви (1 000 000 елементів), grep прийнятно швидкий, незалежно від того, елемент є першим чи середнім чи останнім у масиві.

Випадки оптимізації для довших масивів:

Якщо ваш масив відсортований , використовуйте "двійковий пошук".

Якщо один і той же масив багато разів шукають , скопіюйте його спочатку в хеш, а потім перевірте хеш. Якщо пам'ять викликає занепокоєння, то перемістіть кожен елемент з масиву в хеш. Більш ефективна пам'ять, але знищує вихідний масив.

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

Зауважте: ці оптимізації будуть швидшими лише при роботі з довгими масивами. Не надмірно оптимізуйте.


12
Подвійний тильд був введений в Perl 5.10
Призупинено до подальшого повідомлення.


5
Уникайте розумного збігу у виробничому коді. Це нестабільне / експериментальне очікування до подальшого повідомлення.
Векторний Горгот

1
Я вважаю його також більш читабельним, але не використовуйте, що це не ефективно та перевіряє кожен елемент, навіть якщо він є першим.
giordano

7
Не використовуйте if ("значення" ~~ @array). ~~ - експериментальна функція під назвою Smartmatch. Експеримент, як видається, вважається невдалим і буде видалений або модифікований у майбутній версії Perl.
yahermann

120

Ви можете використовувати функцію smartmatch в Perl 5.10 таким чином:

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

if ( "value" ~~ @array ) 

Для скалярного пошуку робота нижче буде працювати, як вище.

if ($val ~~ @array)

Для вбудованого масиву, який працює внизу, буде працювати як вище.

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

У Perl 5.18 smartmatch позначений як експериментальний, тому потрібно вимкнути попередження, увімкнувши експериментальну прагму, додавши нижче до свого сценарію / модуля:

use experimental 'smartmatch';

Якщо ви хочете уникнути використання smartmatch - тоді, як сказав Аарон, використовуйте:

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

4
Це приємно, але, здається, є новим для Perl 5.10. Минув деякий час, перш ніж я зрозумів, чому я отримую синтаксичні помилки.
Ігор Скочинський

17
Попередження: ви можете уникнути цього, оскільки оператор, мабуть, різну поведінку в різних версіях, а тим часом був позначений як експериментальний . Тому, якщо ви не маєте повного контролю над своєю версією perl (і хто це має), вам, ймовірно, слід уникати цього.
Лабіринт

1
Мені подобається це пояснення щодо того, чому use experimental 'smartmatch'рекомендується налаштування . Оскільки я контролюю свою версію perl (внутрішню систему), я використовую no warnings 'experimental::smartmatch';оператор.
lepe

43

Це повідомлення в блозі обговорюються найкращі відповіді на це питання.

Якщо короткий підсумок, якщо ви можете встановити модулі CPAN, найбільш читабельними рішеннями є:

any(@ingredients) eq 'flour';

або

@ingredients->contains('flour');

Однак більш поширеною фразою є:

any { $_ eq 'flour' } @ingredients

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

any() припиниться, як тільки знайде вашу цінність.

Перегляньте повідомлення в блозі для отримання більш детальної інформації.


8
будь-які потребиuse List::Util qw(any);. List::Utilзнаходиться в основних модулях .
Тількиробота

13

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

Намагайтеся уникати використання grep, якщо дивитесь на ресурси.

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

Спосіб 2: Лінійний пошук

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

Спосіб 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);
@badparams = (1,2,3);
$value = 1;
print "found" if any {$_ == $value} @badparams;

12

Базовий показник @ 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 Щоправда, але якщо вас цікавить лише одна перевірка, а не кілька перевірок, то, очевидно, це мікрооптимізація, щоб навіть замислитися про те, який метод швидший, оскільки ці мікросекунди не мають значення. Якщо ви хочете повторити цю перевірку, хеш - це шлях, адже вартість створення хешу один раз є достатньо малою, щоб потім ігнорувати. Наведені вище показники вимірюють лише різні способи тестування, не враховуючи налаштувань. Так, це може бути недійсним у вашому випадку використання, але знову ж таки - якщо ви робите лише одну перевірку, ви повинні використовувати все, що можна зрозуміти вам та вашим товаришам.
Xaerxess

10

Незважаючи на те, що це зручно у використанні, здається, що рішення конвертувати в хеш коштує досить багато продуктивності, що було проблемою для мене.

#!/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)

5
Використання List::Util::firstшвидше, оскільки воно зупиняє ітерацію, коли знаходить відповідність.
RobEarl

1
-1 Ваш тест має дефекти, grepце значно повільніше , ніж створення хеш і робити пошук, так як перше є О (п) і останнього висновку (1). Просто створіть хеш лише один раз (за межами циклу) і попередньо обчислюйте регулярний вимір для вимірювання лише методів ( див. Мою відповідь ).
Xaerxess

4
@Xaerxess: У моєму випадку я хотів зробити один пошук, тому я вважаю, що справедливо рахувати як створення хеш-регексу, так і пошук / grep. Завдання полягало б у тому, щоб зробити багато пошукових запитів, я думаю, що ваше рішення краще.
aksel

3
Якщо ви хочете зробити лише одну ітерацію, різниця не відрізняється між будь-яким із обраних вами методів, тому будь-який орієнтир є неправильним, оскільки в цьому випадку це злі мікрооптимізація.
Xaerxess

2
Регекс складається лише один раз, оскільки він має прапор 'o'.
Апок

3

@files - це наявний масив

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

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

print "\n";

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


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

Є два способи зробити це. Ви можете використовувати значення кидання в хеш для таблиці пошуку, як це запропоновано в інших публікаціях. (Я додам ще одну ідіому.)

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/;

Це рішення повинно бути налаштоване на типи "поганих значень", які ви шукаєте. Знову ж таки, це може бути абсолютно невідповідно для певних типів струн, тому застереження емптор .


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

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

Ви можете перевірити сталість провідних просторів

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.