Хеши: пары ключ-значение
Разбираемся, как Perl хранит данные не по номеру, а по «названию» — через хеши, ближайший родственник словарей Python и объектов JavaScript.
Хеш — переменная Perl, которая хранит данные парами «ключ — значение», где к каждому значению обращаются не по числовому индексу, а по имени-ключу. Sigil хеша — знак
%.
Когда номера элементов неудобны
Массивы из прошлого урока отлично подходят, когда порядок важен, а данные однородны — список имён учеников, список чисел. Но представьте другую задачу: нужно хранить возраст конкретного человека. Можно было бы завести массив и договориться, что элемент с индексом 0 — это возраст Ани, элемент 1 — возраст Бориса, но это неудобно и легко забыть, что где лежит. Куда естественнее сказать: «дай мне возраст по имени человека». Именно для этого в Perl есть хеш — структура данных, где к каждому значению можно обратиться по осмысленному ключу, а не по номеру по порядку. Если вы знакомы с Python, хеш Perl — это практически то же самое, что словарь (dict); в JavaScript ближайший аналог — обычный объект {}.
Объявление хеша
Sigil хеша — знак процента %. Пары ключ-значение перечисляются через запятую, чередуя ключ и значение.
my %vozrast = (
"Anya" => 16,
"Boris" => 17,
"Vera" => 15,
);
print "Vozrast Any: $vozrast{Anya}\n";Вывод:
Vozrast Any: 16Разбираем построчно
Строка my %vozrast = (...) объявляет хеш с sigil %, потому что мы описываем всю коллекцию пар целиком. Внутри скобок пары записаны через специальную «жирную стрелку» => — это просто более наглядная замена обычной запятой, которая визуально показывает, что слева ключ, а справа — его значение ("Anya" => 16 означает ровно то же самое, что и "Anya", 16, но читается понятнее).
А вот обращение к одному конкретному значению снова меняет sigil на $ — точно так же, как с элементом массива в прошлом уроке. Запись $vozrast{Anya} означает «дай мне из хеша vozrast значение по ключу Anya». Обратите внимание на фигурные скобки {} вместо квадратных [] — это единственное различие в синтаксисе между обращением к элементу массива и обращением к элементу хеша, и его легко перепутать по невнимательности.
Обход хеша циклом
Чтобы пройтись по всем парам хеша, обычно используют функцию keys, которая возвращает список всех ключей, и затем в цикле достают значение по каждому ключу.
my %ocenki = (
"Matematika" => 5,
"Fizika" => 4,
"Informatika" => 5,
);
foreach my $predmet (keys %ocenki) {
print "$predmet: $ocenki{$predmet}\n";
}Вывод:
Matematika: 5
Fizika: 4
Informatika: 5Функция keys %ocenki возвращает массив-список всех ключей хеша ("Matematika", "Fizika", "Informatika"), и цикл foreach проходит по ним по очереди, точно как по обычному массиву из прошлого урока. Внутри цикла переменная $predmet на каждом шаге содержит один ключ (скаляр — поэтому $), а $ocenki{$predmet} достаёт значение по этому ключу — заметьте, что внутри фигурных скобок стоит уже переменная $predmet, а не текст в кавычках, потому что мы подставляем ключ, а не пишем его буквально.
Важная деталь: порядок, в котором keys возвращает ключи, не гарантированно совпадает с порядком, в котором вы их вписывали при создании хеша — в отличие от массива, хеш не хранит порядок элементов. Если порядок важен, дополнительно сортируют ключи функцией sort.
Проверка существования ключа
Иногда нужно узнать, есть ли вообще такой ключ в хеше, не пытаясь сразу получить значение — для этого есть функция exists.
my %telefony = (
"Anya" => "111-22-33",
);
if (exists $telefony{Anya}) {
print "Nomer Any est: $telefony{Anya}\n";
}
if (!exists $telefony{Boris}) {
print "Nomera Borisa net v spravochnike\n";
}Вывод:
Nomer Any est: 111-22-33
Nomera Borisa net v spravochnikeФункция exists возвращает истину или ложь, не пытаясь ничего доставать из хеша по несуществующему ключу. Это отличается от простой проверки самого значения (if ($telefony{Boris})), потому что значением по ключу теоретически может быть и 0, и пустая строка — тогда простая проверка ошибочно скажет «значения нет», хотя ключ на самом деле существует, просто хранит «пустое» значение. exists проверяет именно наличие самого ключа, а не то, что по нему лежит.
Мини-пример: словарь оценок класса
Соберём идеи урока в небольшой практической задаче — хеш с оценками и подсчёт, у кого из учеников оценка ниже четырёх.
my %klass = (
"Anya" => 5,
"Boris" => 3,
"Vera" => 4,
);
foreach my $imya (keys %klass) {
if ($klass{$imya} < 4) {
print "$imya: nuzhna pomosch (otsenka $klass{$imya})\n";
}
}Вывод:
Boris: nuzhna pomosch (otsenka 3)Здесь мы обходим хеш %klass, и для каждого ученика проверяем его оценку через $klass{$imya}, сравнивая с числом 4 обычным оператором <. Только у Бориса оценка меньше четырёх, поэтому в выводе — только одна строка.
Частые ошибки новичков
Первая ошибка — использовать квадратные скобки [] вместо фигурных {} при обращении к элементу хеша, по привычке от массивов. Запись $hash[key] вместо $hash{key} — рабочий Perl-синтаксис, но означает совсем другое (обращение к массиву с именем hash по числовому индексу), и приведёт к путанице, а не к ошибке компиляции, что особенно коварно.
Вторая ошибка — забыть sigil $ при обращении к одному значению хеша, написав %hash{key} вместо $hash{key} — как и с массивами, sigil при обращении к одному элементу всегда $, а не sigil самой коллекции.
Третья ошибка — рассчитывать на порядок ключей при выводе через keys, не задумываясь о том, что хеш этот порядок не гарантирует. Если нужен предсказуемый порядок (например, по алфавиту), ключи нужно явно сортировать.
Итоги
- Хеш хранит пары «ключ — значение», sigil хеша —
%, аналог словаря Python или объекта JavaScript. - Обращение к одному значению идёт через
$hash{ключ}— фигурные скобки, а не квадратные, и sigil$, потому что это одно значение. - Функция
keys %hashвозвращает список всех ключей — по нему удобно проходить цикломforeach. - Функция
exists $hash{ключ}проверяет, есть ли такой ключ вообще, независимо от того, что по нему хранится. - Хеш не хранит порядок элементов — если порядок важен, ключи нужно сортировать отдельно.