Який найбезпечніший спосіб ітерації за допомогою ключів хеша Perl?


107

Якщо у мене є хеш Perl з купою (ключ, значення) пар, який є кращим методом ітерації через усі клавіші? Я чув, що використання eachпевним чином може мати побічні ефекти. Отже, це правда, і найкращий один із двох наступних методів, чи є кращий спосіб?

# Method 1
while (my ($key, $value) = each(%hash)) {
    # Something
}

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}

Відповіді:


199

Основне правило - використовувати функцію, найбільш відповідну вашим потребам.

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

foreach my $key (keys %hash) { ... }

Якщо ви просто хочете значення, використовуйте значення ():

foreach my $val (values %hash) { ... }

Якщо вам потрібні ключі та значення, використовуйте кожне ():

keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop
while(my($k, $v) = each %hash) { ... }

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

%h = (a => 1, b => 2);

foreach my $k (keys %h)
{
  $h{uc $k} = $h{$k} * 2;
}

отримання очікуваного хешу:

(a => 1, A => 2, b => 2, B => 4)

Але за допомогою кожного () зробити те ж саме:

%h = (a => 1, b => 2);

keys %h;
while(my($k, $v) = each %h)
{
  $h{uc $k} = $h{$k} * 2; # BAD IDEA!
}

важко передбачити невірні результати. Наприклад:

(a => 1, A => 2, b => 2, B => 8)

Однак це безпечно:

keys %h;
while(my($k, $v) = each %h)
{
  if(...)
  {
    delete $h{$k}; # This is safe
  }
}

Все це описано в документації на Perl:

% perldoc -f keys
% perldoc -f each

6
Будь ласка, додайте недійсні контекстні ключі% h; перед кожним циклом, щоб безпечно показати його, використовуючи ітератор.
ysth

5
З кожним є ще один застереження. Ітератор прив’язаний до хеша, а не до контексту, а це означає, що він не є повторним учасником. Наприклад, якщо ви переведіть цикл на хеш і надрукуєте хеш-перл, внутрішньо скине ітератор, зробивши цей цикл коду нескінченним: мій% хеш = (a => 1, b => 2, c => 3,); while (мій ($ k, $ v) = кожен% хеш) {print% хеш; } Детальніше читайте на blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Роулер

28

Одне, про що слід пам’ятати, використовуючи, eachце те, що він має побічний ефект від додавання «стану» до вашого хешу (хеш повинен пам’ятати, що таке «наступний» ключ). Якщо ви використовуєте такий код, як опубліковані вище фрагменти, які повторюють весь хеш за один раз, це, як правило, не є проблемою. Однак ви зіткнетеся з проблемами (я кажу з досвіду;), коли ви користуєтесь eachразом із висловлюваннями на кшталт lastабо returnдля виходу з while ... eachциклу, перш ніж обробити всі ключі.

У цьому випадку хеш запам’ятає, які ключі він уже повернув, і коли ви eachйого будете використовувати наступного разу (можливо, в абсолютно непов'язаному фрагменті коду), він продовжить у цій позиції.

Приклад:

my %hash = ( foo => 1, bar => 2, baz => 3, quux => 4 );

# find key 'baz'
while ( my ($k, $v) = each %hash ) {
    print "found key $k\n";
    last if $k eq 'baz'; # found it!
}

# later ...

print "the hash contains:\n";

# iterate over all keys:
while ( my ($k, $v) = each %hash ) {
    print "$k => $v\n";
}

Це відбитки:

found key bar
found key baz
the hash contains:
quux => 4
foo => 1

Що трапилося з клавішами "bar" і baz "Вони все ще є, але друга eachпочинається там, де перша зупинилася, і зупиняється, коли вона доходить до кінця хешу, тому ми ніколи не бачимо їх у другій петлі.


22

Місце, де eachможуть виникнути у вас проблеми, - це справжній ітератор, що не має рамки. Як приклад:

while ( my ($key,$val) = each %a_hash ) {
    print "$key => $val\n";
    last if $val; #exits loop when $val is true
}

# but "each" hasn't reset!!
while ( my ($key,$val) = each %a_hash ) {
    # continues where the last loop left off
    print "$key => $val\n";
}

Якщо вам потрібно бути впевненим, що eachотримує всі ключі та значення, вам потрібно переконатися, що ви використовуєте keysабо valuesспочатку (як це скидає ітератор). Дивіться документацію для кожного .


14

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

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

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


6

Кілька різноманітних думок на цю тему:

  1. Немає нічого небезпечного в тому, що стосується самих ітераторів хешу. Небезпечно - це зміни ключів хеша, поки ви переглядаєте його. (Цілком безпечно змінювати значення.) Єдиний потенційний побічний ефект, про який я можу придумати, - це те, що valuesповертає псевдоніми, це означає, що зміна їх змінить вміст хешу. Це задумано, але може не бути тим, що ви хочете за деяких обставин.
  2. Прийнята відповідь Джона хороша за одним винятком: в документації зрозуміло, що додавати ключі під час ітерації над хешем не можна. Він може працювати для деяких наборів даних, але не працюватиме для інших залежно від порядку хешу.
  3. Як уже зазначалося, останнє повернене ключем можна безпечно видалити each. Це НЕ вірно для , keysяк eachце итератор , а keysповертає список.

2
Повторно "не вірно для ключів", скоріше: це не стосується клавіш і будь-яке видалення є безпечним. Фрази, які ви використовуєте, означають, що ніколи не безпечно нічого видаляти під час використання клавіш.
ysth

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

3

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


3

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

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


2

Я зазвичай використовую keysі не можу придумати останній раз, коли я користувався чи читав його each.

Не забувайте про це map, залежно від того, що ви робите в циклі!

map { print "$_ => $hash{$_}\n" } keys %hash;

6
не використовуйте карту, якщо ви не хочете повернути значення
ko-dos

-1

Я б сказав:

  1. Використовуйте все, що найпростіше читати / розуміти для більшості людей (тому ключі, як правило, я б заперечував)
  2. Використовуйте все, що вирішите послідовно для всієї бази кодів.

Це дає 2 основні переваги:

  1. Простіше помітити "загальний" код, щоб ви могли повторно розподілити на функції / метіоди.
  2. Майбутнім розробникам простіше підтримувати.

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


1
З keysвикористанням пам'яті збільшується на hash-size * avg-key-size. Зважаючи на те, що розмір ключа обмежений лише пам’яттю (оскільки вони просто елементи масиву типу «їх» відповідних значень під кришкою), в деяких ситуаціях це може бути надмірно дорожчим як для використання пам'яті, так і часу, необхідного для копіювання.
Адріан Гюнтер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.