Глава 5. Хеши
В этой главе:
Что такое хеш
Хеш (*1) похож на массив, который мы рассматривали выше, тем, что представляет собой
набор скалярных данных, отдельные элементы которого выбираются по индексному значению.
В отличие от массива, индексные значения хеша — не малые неотрицательные целые, а
произвольные скаляры. Эти скаляры (называемые ключами) используются для выборки значений
из массива.
Элементы хеша не стоят в каком-то конкретном порядке. Можете рассматривать их как
стопку библиографических карточек. Верхняя половина каждой карточки — это ключ, а
нижняя — значение. Каждый раз, когда вы помещаете в хеш значение, создается новая
карточка. Когда нужно изменить значение, вы указываете ключ, и Perl находит необходимую
карточку. Поэтому порядок карточек, по сути дела, роли не играет. Perl хранит все
карточки (т.е. пары ключ-значение) в особом внутреннем порядке, который облегчает
поиск конкретной карточки, поэтому при поиске не приходится просматривать все пары.
Порядок хранения карточек изменять нельзя, так что даже и не пытайтесь (*2).
Хеш-переменные
Имя хеш-переменной состоит из знака процента % и буквы, за которой могут идти
другие буквы, цифры и знаки подчеркивания числом от нуля и больше. Другими словами,
часть имени после знака процента похожа на соответствующую часть имен скалярных
переменных и массивов. Кроме того, точно так же, как нет никакой связи между
$fred и @fred, хеш-переменная %fred не имеет ничего общего с названными объектами.
Чаще всего хеш создается и используется путем обращения к его элементам, а не
ко всему хешу. Каждый элемент хеша — отдельная скалярная переменная, доступная
через индекс, представляющий собой строковое значение и называемый ключом. Так,
обращение к элементам хеша %fred производится путем указания $fred{$ключ}, где
$ключ — любое скалярное выражение. Вновь подчеркнем, что обращение к элементу хеша
требует иного синтаксиса, нежели обращение ко всему хешу целиком.
Как и в случае с массивами, новые элементы хеша создаются путем присваивания значения:
$fred{"ааа"} = "bbb";
#создает ключ "ааа", значение "bbb"
$fred(234.5} = 456.7;
# создает ключ "234.5", значение 456.7
С помощью этих операторов в хеше создаются два элемента. При последующем обращении
к элементам (по указанным ключам) возвращаются ранее записанные значения:
print $fred("ааа");
# выводит на экран "bbb"
$fred{234.5) += 3;
# делает значение равным 459.7
При обращении к несуществующему элементу возвращается значение undef (как и
при обращении к отсутствующему элементу массива или к неопределенной скалярной
переменной).
Литеральное представление хеша
У вас может возникнуть необходимость обратиться к хешу целиком — например,
чтобы инициализировать его или скопировать в другой хеш. Фактически в Perl никакого
литерального формата для хеша не предусмотрено, поэтому он просто представляется в
виде списка. Каждая пара элементов этого списка (в котором всегда должно быть четное
число элементов) задает ключ и соответствующее значение. Это развернутое представление
может быть присвоено другому хешу, который затем воссоздаст тот же самый хеш. Другими
словами:
@fred_list = %fred;
# @fred_list получает значение
# ("ааа","bbb","234.5","456.7")
%barney = @fred_list;
# создать %barney как %fred
%barney = %fred;
# ускоренный метод выполнения этой задачи
%smooth = ("ааа","bbb","234.5","456.7");
#создать %smooth как %fred из литеральных значений
Порядок пар ключ-значение в этом развернутом формате произвольный и контролю не
поддается. Даже если вы меняете местами какие-то значения или создаете хеш целиком,
возвращаемый развернутый список все равно будет стоять в том порядке, который Perl
выбрал для обеспечения эффективного доступа к отдельным элементам. Ни на какую конкретную
последовательность рассчитывать нельзя.
Одно из применений такого свертывания-развертывания — копирование хеш-значения в
другую хеш-переменную:
%сору = %original;
# копировать из %original в %сору
Используя операцию reverse, можно создать хеш, в котором ключи и значения
поменяются местами:
%backwards = reverse %normal;
Конечно, если %normal имеет два идентичных значения, то в %backwards
они превратятся в один элемент, поэтому данную операцию лучше всего
выполнять только над хешами с уникальными ключами и значениями.
Хеш-функции
В этом разделе перечислены некоторые функции, предназначениые для обработки хешей.
Функция keys
Функция keys(%имя_хеша) выдает список всех текущих ключей,
имеющихся в хеше %имя_хеша. Другими словами, применение этой функции
эквивалентно возвращению всех элементов списка с нечетными номерами (первый,
третий, пятый и т.д.) путем развертывания хеша %имя_хеша в списочном контексте,
причем функция keys возвращает их именно в этом порядке. Если элементы в хеше
отсутствуют, функция keys возвращает пустой список. Применим эту функцию к
хешу из предыдущих примеров:
$fred("aaa"} = "bbb";
$fred{234.5) = 456.7;
@list = keys(%fred);
# @list получает значение ("ааа", 234.5)
# или (234.5, "ааа")
Как и во всех остальных встроенных функциях, круглые скобки не
обязательны: функция keys %fred полностью идентична keys (%fred).
foreach $key (keys (%fred))
{
# однократно для каждого значения хеша %fred
print "at $key we have $fred($key}\n";
# показать ключ и значение
}
В этом примере показано также, что отдельные элементы хеша могут интерполироваться
в строки в двойных кавычках. Весь хеш, однако, интерполировать таким образом нельзя (*3).
В скалярном контексте функция keys выдает число элементов (пар ключ-значение), содержащихся
в хеше. Например, вы можете выяснить, пуст ли хеш, так:
if (keys(%fred))
{
# если keys() не равно 0, массив не пустой
...;
}
# или
while (keys(%fred) < 10)
{
# продолжать цикл, пока меньше 10 элементов
...;
}
Для того чтобы узнать, пуст хеш или нет, можно просто использовать хеш в
скалярном контексте:
if (%fred)
{
# если "истина", в нем что-то есть
# что-то сделать
}
Функция values
Функция values(%имя_массива) возвращает список всех текущих значений указанного
массива в том же порядке, в каком функция keys(%имя_массива} возвращает ключи.
Как всегда, круглые скобки не обязательны. Например:
%lastname = 0;
# сделать %lastname пустым
$lastname("fred"} = "flintstone";
$lastname("barney"} = "rubble";
@lastnames = values(%lastname);
# получить значения
Массив @lastnames будет содержать либо значения
("flintstone", "rubble"), либо ("rubble", "flintstone").
Функция each
Для выполнения цикла над всем хешем (т.е. для проверки каждого его
элемента) можно использовать функцию keys и получать значения по возвращаемым
ею ключам. Действительно, этот метод широко используется, но есть и более эффективный
способ — функция each (%имя_хеша), которая возвращает пару ключ-значение как
двухэлементный список. При каждом вычислении этой функции для одного хеша
возвращается очередная пара ключ-значение, пока не будут проверены все элементы.
Если пар больше нет, each возвращает пустой список.
Например, чтобы пройти по хешу %lastname из предыдущего примера, нужно использовать
нечто такое:
while (($first,$last) = each(%lastname))
{
print "The last name of $first is $last\n";
}
Присваивание нового значения всему хешу заставляет функцию each перейти в
его начало. Добавление элементов в хеш и удаление из него элементов в ходе
выполнения цикла вполне может "запутать" функцию each (и вас, наверное, тоже).
Функция delete
Итак, вы можете добавлять элементы в хеш, но пока не можете удалять их (кроме
как путем присваивания нового значения всему хешу). Для удаления элементов хеша
в Perl используется функция delete. Операнд этой функции — хеш-ссылка, аналогичная
той, которая используется для извлечения конкретного значения из хеша. Perl удаляет
из хеша соответствующую ссылке пару ключ-значение. Например:
%fred = ("aaa","bbb",234.5,34.56);
# добавить в %fred два элемента
delete $fred("aaa"};
# теперь в хеше %fred только одна пара ключ-значение
Срезы хешей
Как и в случае с переменной-массивом (или списочным литералом), можно воспользоваться
срезом хеша, что даст возможность обращаться не к одному его элементу, а одновременно
к набору элементов. Возьмем, к примеру, результаты игры в кегли:
$score{"fred"} = 205;
$score{"barney"} = 195;
$score{"dino"} = 30;
Все это можно записать одной строкой:
($score{"fred"}, $score{"barney"}, $score{"dino"}) = (205,195,30);
Но даже она слишком длинна, поэтому давайте используем срез хеша:
@score{"fred","barney","dino"} = (205,195,30);
Вот так гораздо короче. Можно сочетать использование среза хеша и
интерполяции переменных:
@players = qw(fred barney dino);
print "scores are: @score(@players}\n";
Срезы хешей можно также использовать для слияния небольшого хеша с более крупным.
В этом примере меньший хеш имеет приоритет в том смысле, что при наличии ключей-дубликатов
используется значение из меньшего хеша:
@league { keys %score } = (values %score);
Здесь значения хеша %score сливаются с хешем %league. Это эквивалентно выполнению
гораздо более медленной операции:
%league = (%league, %score); # слить %score с %league
Упражнения
- Напишите программу, которая читает строку, а затем выводит эту строку и соответствующее ей значение согласно приведенной ниже таблице:
| Ввод |
red |
green |
blue |
| Вывод |
apple |
leaves |
ocean |
- Напишите программу, которая читает ряд слов (по одному в строке) до конца файла, а затем выводит на экран сводку о том, сколько раз встретилось каждое слово. (Дополнительная задача: отсортируйте появляющиеся на экране слова по их ASCII-значениям в порядке возрастания последних.)
Ответы к упражнениям даны в приложении А.
Примечания
(*1) В старой документации хеши назывались ассоциативными массивами, но мы настолько
устали применять к столь распространенному понятию такой многосложный термин, что
решили заменить его гораздо более удачным односложным словом.
(*2) Модули типа IxHash и DB_fiIe обеспечивают некоторую степень упорядочения, но
ценой существенного снижения производительности.
(*3) Можно, в принципе, с помощью среза, но здесь о срезах мы не говорим.
| Назад
| Вперед |

