Как пройти по хешу в Perl

Задача. В коде Perl программы есть хеш %h. В этом хеше есть какие-то пары значений. Нужно пройтись по всем элементам хеша и выполнить действия с каждым элементом.

Пройтись по хешу с помощью foreach

Чаще всего, самый удобный способ для того чтобы обойти хеш — это использовать foreach. Вот пример кода:

▶ Run
#!/usr/bin/perl

my %h = (
    a => 1,
    b => 20,
    asdf => 'Hello',
);

foreach my $key (keys %h) {
    print "$key - $h{$key}\n";
}

Тут все достаточно просто. Сначала мы создаем хеш %h в котором есть три пары значений. Дальше находится цикл. С помощью keys %h мы получили список всех ключей хеша (в данном примере это строки 'a', 'b' и 'asdf'). Строка с ключом помещается в переменную $key и выполняется тело цикла, потом в $key помещается следующий ключ хеша и снова выполняется тело цикла, и так происходит для всех ключей.

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

b - 20
asdf - Hello
a - 1

Хеш — это неупорядоченный набор пар значений. И по выводу это хорошо видно — тот порядок в котором были выведены элементы хеша не совпадают с тем порядком в котором они были определены в коде программы. Причем если запускать этот скрипт несколько раз, то порядок элементов в выводе будет меняться. keys %h возвращает список ключей хеша в произвольном порядке. Если же нужно чтобы порядок был всегда одинаковый, то для этого, например, можно использовать, сортировку foreach my $key (sort keys %h) {.

Пройти по хешу с помощью while и each

Другой способ для того чтобы обойти весь хеш — это использовать цикл while и ключевое слово each. Вот код, который работает так же как и предыдущий пример:

▶ Run
#!/usr/bin/perl

my %h = (
    a => 1,
    b => 20,
    asdf => 'Hello',
);

while (my ($key, $value) = each %h) {
    print "$key - $value\n";
}

each %h возвращает список из двух элементов — ключ хеша и соответствующее ему значение. Точно так же, как и в случае keys порядок в котором each возвращает пары значений не определен. Разные запуски одной и той же программы будут возвращать пары в разных порядках.

Мы присваиваем значения которые вернул each в переменные. each помнит, какие пары хеша он уже выдавал и при следующем использовании вернет другую пары значений. Так будет продолжаться пока each не вернет все пары, после этого он вернет пустой список. Пока each возвращает пару значений выполняется тело while цикла, как только он возвращает пустой список, цикл while завершается.

Изменение значений в хеше

При использовании foreach цикла, keys %h выполняется только один раз. При использовании while цикла each выполняется при каждой итерации.

Расширим предыдущую задачу — нам нужно не только выводить все элементы хеша, но еще и заменить их. Это совершенно без проблем можно сделать при использовании foreach и keys:

▶ Run
#!/usr/bin/perl

use Data::Dumper;

my %h = (
    a => 1,
    b => 20,
    asdf => 'Hello',
);

foreach my $key (keys %h) {
    print "$key - $h{$key}\n";
    $h{ $key . $key } = $h{$key};
    delete $h{$key};
}

print Dumper \%h;

Вывод программы:

asdf - Hello
a - 1
b - 20
$VAR1 = {
          'bb' => 20,
          'aa' => 1,
          'asdfasdf' => 'Hello'
        };

Но так не получится сделать при использовать each:

▶ Run
#!/usr/bin/perl

use Data::Dumper;

my %h = (
    a => 1,
    b => 20,
    asdf => 'Hello',
);

while (my ($key, $value) = each %h) {
    print "$key - $value\n";

    # error!
    $h{ $key . $key } = $value;
    delete $h{$key};
}

print Dumper \%h;

При запуске этой программы на экране появится что-то вроде:

Use of each() on hash after insertion without resetting hash iterator results in undefined behavior at script.pl line 11.
Use of each() on hash after insertion without resetting hash iterator results in undefined behavior at script.pl line 11.
Use of each() on hash after insertion without resetting hash iterator results in undefined behavior at script.pl line 11.
Use of each() on hash after insertion without resetting hash iterator results in undefined behavior at script.pl line 11.
b - 20
bb - 20
bbbb - 20
asdf - Hello
$VAR1 = {
          'asdfasdf' => 'Hello',
          'bbbbbbbb' => 20,
          'a' => 1
        };

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

Так что если при обходе хеша нужно его изменить, то нужно использовать foreach и keys. При использовании while и each программа будет работать некорректно.

Связанные темы

Другие статьи

Комментарии