Вложенные структуры данных
В прошлом уроке мы разобрались, как из ссылок собрать массив массивов — своего рода таблицу. Но реальные данные редко бывают такими однородными. Гораздо чаще нужно хранить что-то вроде «у каждого ученика — список его оценок» или «список учеников, и у каждого — набор характеристик». Это и есть хеш массивов и массив хешей — два самых ходовых способа строить вложенные структуры в Perl.
Зачем это нужно
Хеш — это структура «ключ → значение», удобная, когда данные подписаны по имени, а не по номеру позиции. Но что, если значением должен быть не одно число, а сразу несколько? Например, у одного ученика может быть несколько оценок по одному предмету. Тогда значением хеша становится не скаляр, а ссылка на массив — и получается хеш массивов.
А если, наоборот, нужно перечислить много «карточек» с одинаковым набором полей — учеников, товаров, сотрудников — то удобнее массив, где каждый элемент — ссылка на хеш с полями этой карточки. Это массив хешей, и он встречается в реальном коде даже чаще: так на Perl обычно представляют результаты запроса к базе данных или строки из CSV-файла.
Хеш массивов
Идея та же, что и в прошлом уроке: значением хеша делаем не число, а ссылку на массив.
my %grades = (
Anna => [5, 4, 5],
Boris => [3, 4, 4],
);
print $grades{Anna}->[0];
print "\n";
print $grades{Boris}->[2];Вывод:
5
4Здесь %grades — обычный хеш, но каждое его значение — это анонимный массив вроде [5, 4, 5], то есть ссылка на массив с оценками. Обращение $grades{Anna} берёт значение по ключу Anna — это ссылка на массив. Дальше ->[0] разыменовывает её и берёт первый элемент. Как и в прошлом уроке, стрелку между фигурной и квадратной скобкой можно опустить: $grades{Anna}[0] работает так же.
Чтобы добавить новую оценку существующему ученику, разыменовываем ссылку и работаем с массивом как обычно:
my %grades = ( Anna => [5, 4, 5] );
push @{ $grades{Anna} }, 5;
print join(", ", @{ $grades{Anna} });Вывод:
5, 4, 5, 5Конструкция @{ $grades{Anna} } — это «блочное» разыменование: фигурные скобки после сигила @ говорят Perl «здесь ссылка на массив, достань из неё сам массив целиком». Такая запись нужна, когда работаем не с одним элементом, а со всем массивом сразу — например, чтобы передать его в push или join.
Массив хешей
Теперь перевернём структуру: массив, каждый элемент которого — ссылка на хеш.
my @students = (
{ name => "Anna", age => 15 },
{ name => "Boris", age => 16 },
);
print $students[0]->{name};
print "\n";
print $students[1]->{age};Вывод:
Anna
16Каждая пара фигурных скобок { name => "Anna", age => 15 } создаёт анонимный хеш и ссылку на него — точно так же, как квадратные скобки создавали анонимный массив. Элемент $students[0] — это ссылка на первый хеш-«карточку». Дальше ->{name} разыменовывает её и берёт значение по ключу name.
Такая структура особенно удобна для перебора в цикле — каждый элемент массива представляет собой отдельную запись со своими полями:
my @students = (
{ name => "Anna", age => 15 },
{ name => "Boris", age => 16 },
);
foreach my $student (@students) {
print $student->{name} . " is " . $student->{age} . " years old\n";
}Вывод:
Anna is 15 years old
Boris is 16 years oldНа каждой итерации $student получает ссылку на очередной хеш из @students, и через ->{...} мы читаем нужные поля. Это один из самых частых паттернов в реальном Perl-коде — обработка списка записей одинаковой формы.
Комбинируем: хеш хешей и глубже
Уровни вложенности можно комбинировать сколько угодно — просто на каждом уровне появляется своя стрелка. Например, у каждого ученика может быть не список оценок, а хеш «предмет → оценка»:
my %students = (
Anna => { math => 5, physics => 4 },
Boris => { math => 3, physics => 4 },
);
print $students{Anna}->{math};
print "\n";
print $students{Boris}->{physics};Вывод:
5
4Это хеш хешей: внешний хеш %students по имени ученика возвращает ссылку на внутренний хеш с оценками по предметам. Читать такую структуру нужно слева направо, шаг за шагом: сначала $students{Anna} — ссылка на «карточку» Анны, затем ->{math} — конкретная оценка внутри этой карточки.
Частые ошибки
Первая ошибка — попытка обратиться к вложенному значению одной парой скобок, например $students{Anna}{math}{oops}, когда структура на самом деле не такая глубокая. Perl в этом случае не всегда сразу выдаст явную ошибку, а может создать по пути пустые вложенные структуры — это называется autovivification, и в сложном коде из-за него легко случайно «размножить» пустые хеши там, где их не ожидали.
Вторая — путаница между %grades (сам хеш) и $grades{key} (одно его значение). Тип скобок и сигил зависят от того, с чем именно работаем: весь хеш — фигурные скобки с % в объявлении, одно значение — фигурные скобки с $ при обращении.
Третья — забыть фигурные скобки при блочном разыменовании всего массива или хеша. Код push $grades{Anna}, 5 без @{ ... } в современном Perl будет работать не так, как ожидается, или выдаст предупреждение — правильная форма именно push @{ $grades{Anna} }, 5.
Итоги
Хеш массивов и массив хешей строятся по одному и тому же принципу: значением хеша или элементом массива становится ссылка на другую структуру, а не сами данные напрямую. Обращение к вложенным значениям идёт цепочкой стрелок ->, где тип скобок — квадратные или фигурные — подсказывает, что именно на этом уровне: массив или хеш. Массив хешей особенно часто встречается на практике для представления списков однотипных записей, а комбинации вложенности — хеш хешей, массив хешей массивов и так далее — строятся тем же способом, просто с дополнительными уровнями стрелок.