ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
ХэшиВведениеКак люди, так и части компьютерных программ взаимодействуют между собой самым причудливым образом. Отдельные скалярные переменные похожи на отшельников, ведущих замкнутое существование в рамках собственной личности. Массив напоминает партию, где множество индивидуумов объединяется под именем харизматического предводителя. Где-то между ними расположилась удобная ниша, в которой живут совокупности связей "один-к-одному" - хэши. В старой документации по Perl хэши часто назывались ассоциативными массивами, но термин получается слишком длинным. Аналогичные структуры данных существуют и в других языках, где они обозначаются другими терминами - хэш-таб-лицы, таблицы, словари, отображения и даже а-списки, в зависимости от языка. К сожалению, отношения хэшей являются не равными, а подчиненными - например, "Энди - начальник Ната"; "Кровяное давление пациента - 112/62" или "Название журнала с индексом ISSN 1087-903X - The Perl Journal". Хэш всего лишь предоставляет удобные средства для получения ответов на вопросы типа: "Кто является начальником Ната?" или "Как называется журнал 1087-903X"? Вы не сможете спросить "Чьим начальником является Эндн?" Впрочем, поиску ответов на подобные вопросы посвящен один из рецептов этой главы. Однако у хэшей есть свои преимущества. В Perl хэш является встроенным типом данных. Благодаря применению хэшей многие сложные алгоритмы сводятся к простой выборке значений. Кроме того, хэши предоставляют быстрые и удобные средства для построения индексов и таблиц просмотра. Если для простой скалярной переменной применяется идентификатор типа $, а для массива - @, то для хэшей используется идентификатор %. Префикс % относится лишь к ссылкам на хэш в целом. Значение ключа представляет собой скалярную величину, поэтому для него используется символ $ (по аналогии с тем, как для ссылок на отдельный элемент массива используется префикс $). Следовательно, отношение "начальник Ната" должно записываться в виде $boss{"Nat"}. В обычных массивах используются числовые индексы, но индексы хэшей всегда являются строковыми. Ассоциированные значения могут быть произвольными скалярными величинами, в том числе ссылками. Используя ссылки в качестве ассоциированных значений, можно создавать хэши для хранения не только строк и чисел, но и массивов, других хэшей или объектов (вернее, ссылок на массивы, хэши или объекты). Хэши могут инициализироваться с помощью списков, содержащих пары "ключ/ значение":%аgе = ( "Nat", 24, "Jules", 25, "Josh", 17 ); Такая запись эквивалентна следующей: $age{"Nat"} = 24;
%food_color = (
(хэш %i ooa_coior используется во многих примерах этой главы). Такая инициализация также является примером списковой эквивалентности - в некоторых отношениях хэш ведет себя так, словно он является списком пар "ключ/значение". Мы воспользуемся этим в нескольких рецептах, в частности - для объединения и инвертирования. В отличие от обычной занятой, оператор => обладает особым свойством: любое предшествующее ему слово интерпретируется как строковое значение. Это позволяет убрать кавычки и сделать программу более попятной. Однословные ключи хэшей также автоматически интерпретируются как строки, поэтому вместо $hash{"somekey"} можно написать просто $hash{somekey}. Приведенная выше инициализация %food_color записывается в следующем виде: %food_color = (
> Смотри также --------------------------------
5.1. Занесение элемента в хэшПроблемаТребуется добавить в хэш новый элемент.РешениеПрисвоите нужное значение в записи вида:$ХЭШ{$КЛЮЧ} = $ЗНАЧЕНИЕ; КомментарийПропесс занесения данных в хэш весьма тривиален. В языках, где хэш не относится к встроенным типам данных, приходится беспокоиться о переполнении, изменении размеров и коллизиях в хэш-таблицах. В Perl обычное присваивание решает сразу все проблемы. Если ключ уже занят, то есть содержит предыдущее значение, память автоматически освобождается (по аналогии с присваиванием скалярной переменной).# Хэш %food_color определяется во введении $food_color{Raspberry} = "pink": print "Known foods:\n"; foreach $food (keys %food_color) { print "$food\n"; } Known foods:
Если в качестве ключа хэша используется неопределенная величина undef, она преобразуется в пустую строку "" (что сопровождается предупреждением при запуске с параметром -w). Вероятно, неопределенный ключ undef - это не то, что вы хотели. С другой стороны, undef является вполне допустимым значением в хэ-шах. Однако при выборке значения для ключа, отсутствующего в хэше, вы также получите undef. Это означает, что для проверки существования ключа $key в хэше %hash простая логическая проверка if ($hash{$key}) не подходит. Присутствие ключа в хэше проверяется записью вида exists($hash{$key}); определенность ассоциированного значения - defined($hash{$key}), а его истинность - if ($hash{$key} /. Во внутренних алгоритмах хэширования Perl перестановки строки попадают на одну и ту же позицию. Если в ключах хэша многократно встречаются перестановки одной строки (скажем, "spare" и "craps"), быстродействие хэша заметно падает. На практике это происходит редко. > Смотри также ------------------------------- Раздел "List Value Constructors" peiidata(1) рецепт 5.2. 5.2. Проверка наличия ключа в хэшеПроблемаТребуется узнать, содержит ли хэш конкретный ключ независимо от ассоциированного с ним значения.РешениеВоспользуйтесь функцией exists:# Содержит ли %ХЭШ ключ $КЛЮЧ?
КомментарийВ следующем фрагменте функция exists проверяет, присутствует ли ключ в хэше %food_color:# Хэш %food_color определяется во введении
%аgе = ();
%name = ();
Замена неправильной строки следующим вызовом exists позволяет пропускать нулевые и несуществующие файлы: next if exists $name{$_}; В самом первом примере предполагается, что все, что не является едой (food), относится к напиткам (dnnk). В реальном мире подобные допущения весьма опасны. > Смотри также -------------------------------
5.3. Удаление из хэшаПроблемаТребуется удалить элемент из хэша, чтобы он не опознавался функцией keys, values или each. Например, если в хэше имена работников ассоциируются с окладами, после увольнения работника необходимо удалить его строку из хэша.РешениеВоспользуйтесь функцией delete: # Удалить $КЛЮЧ и ассоциированное значение из хэша %ХЭШ с)е1е1е($ХЭШ{$КЛЮЧ});КомментарийМногие ошибочно пытаются удалять элементы из хэша с помощью undef - undef ${ХЭШ{$КЛЮЧ} или $ХЭШ{$КЛЮЧ} = undef. В обоих случаях в хэше будет присутствовать элемент с ключом $КЛЮЧ и значением undef. Функция delete - единственное средство для удаления конкретных элементов из хэша. Удаленный элемент не появится ни в списке keys, ни в итерациях each; функция exists возвращает для него ложное значение. Следующий фрагмент демонстрирует отличия undef от delete:# Хэш %food_color определяется во введении
Initially:
> Смотри также --------------------------------
5.4. Перебор хэшаПроблемаТребуется выполнить некоторые действия с каждым элементом (то есть парой "ключ/значение") хэша.РешениеВоспользуйтесь функцией each в цикле while:while(($Kлюч, $ЗНАЧЕНИЕ) = each(%ХЭШ)) { # Сделать что-то,с $КЛЮЧ и $ЗНАЧЕНИЕ } Если хэш не очень велик, можно вызвать keys в цикле fоreach: foreach $КЛЮЧ (keys %ХЭШ) { $ЗНАЧЕНИЕ = $ХЭШ{$КЛЮЧ}; # Сделать что-то с $КЛЮЧ и $ЗКАЧЕНИЕ } КомментарийСледующий простой пример перебирает элементы хэша %food_color из введения:# Хэш %food_color определяется во введении while(($food, $color.) = each(%food_color)) { print "$food is $color.\n"; } Banana is yellow. Apple is red. Carrot is orange. Lemon is yellow. В примере с foreach можно обойтись без переменной $со1ос, поскольку она используется всего один раз. Достаточно написать: print "Stood is $food_color{$food}.\n". При каждом вызове each для одного и того же хэша функция возвращает "следующую" пару ключ/значение. Слово "следующую" взято в кавычки, потому что пары возвращаются в порядке, соответствующем внутренней структуре хэша, и этот порядок почти никогда не совпадает с числовым или алфавитным. За последним элементом each возвращает пустой список (); результат интерпретируется как ложный, и цикл while завершается. В примере с foreach использована функция keys, которая строит список всех ключей из хэша еще перед началом выполнения цикла. Преимущество each заключается в том, что пары "ключ/значение" извлекаются по одной. Если хэш содержит много ключей, отказ от предварительного построения полного списка существенно экономит память и время. Однако функция each не позволяет управлять порядком обработки пар. Применение foreach и keys для перебора списка позволяет установить свой порядок обработки. Предположим, нам понадобилось вывести содержимое хэша в алфавитном порядке ключей: foreach $food (sort keys %food_color) { print "$food is $food_color{$food}.\n"; } Apple is red. Banana is yellow. Carrot is orange. Lemon is yellow. Подобное применение to reach встречается довольно часто. Функция keys строит список ключей в хэше, после чего to reach перебирает их. Если хэш состоит из большого числа элементов, возникает опасность, что возвращаемый keys список займет много памяти. Приходится выбирать между затратами памяти и возможностью обработки элементов в определенном порядке. Сортировка подробнее рассматривается в рецепте 5.9. Поскольку функции keys, values и each используют одни и те же внутренние структуры данных, следует внимательно следить за чередованием вызовов этих функций или преждевременным выходом из цикла each. При каждом вызове keys или values текущая позиция each сбрасывается. Следующий фрагмент зацикливается и бесконечно выводит первый ключ, возвращаемый each: while ( ($k,$v) = each %food_color) {
#!/usr/bin/perl
[> Смотри также --------------------------------
5.5. Вывод содержимого хэшаПроблемаТребуется вывести содержимое хэша, однако конструкции print "%ХЭШ" и print %ХЭШ не работают.РешениеОдно из возможных решений - перебрать все нары "ключ/значение" в хэше (см. рецепт 5.4) и вывести их:while ( ($k,$v) = each %hash) { print "$k => $v\n";
КомментарийВсе перечисленные приемы обладают различными возможностями по управлению порядком н форматированием вывода, а также различной эффективностью. Первый способ (перебор хэша) чрезвычайно гибок и эффективен но затратам памяти. Вы можете как угодно форматировать выходные данные, при этом понадобятся всего две скалярные переменные - текущий ключ и значение. Использование цикла foreach позволяет вывести хэш с упорядочением ключей (ценой построения отсортированного списка):foreach $k (sort keys %hash) { print "$k => $hash{$k}\n"; } Функция map не уступает перебору по богатству возможностей. Сортировка ключей по-прежнему позволяет работать с элементами в произвольном порядке, Выходные данные можно как угодно срорматировать. На этот раз создастся список строк (например, "КЛЮЧ==>ЗНАЧЕНИЕ", как в приведенном выше примере), передаваемый print. Два последних приема представляют собой фокусы, связанные с интерполяцией. Интерпретация хэша как списка не позволяет предсказать или управлять порядком вывода пар "ключ/значение". Более того, данные в этом случае выводятся в виде списка ключей и значений, элементы которого разделяются текущим содержимым переменной $". В отличие от других приемов, вам не удастся вывести каждую пару на новой строке или отделить ключи от значений символом =>. > Смотри также -------------------------------
5.6. Перебор элементов хэша в порядке вставкиПроблемаФункции keys и each извлекают элементы хэша в довольно странном порядке. Вы хотите получить элементы в порядке вставки.РешениеВоспользуйтесь модулем Tie::IxHash.use Tie::IxHash; tie %ХЭШ, "Tie::IxHash"; # Операции с хэшем %ХЭШ @keys = keys %ХЭШ; # Массив @keys отсортирован в порядке вставки КомментарийМодуль Tie::IxHash заставляет функции keys, each и values возвращать элементы в порядке занесения в хэш. Это часто избавляет от необходимости заранее обрабатывать ключи хэша какой-нибудь сложной сортировкой или поддерживать отдельный массив, содержащий ключи в порядке их вставки. Tie::IxHash также представляет объектно-ориентированный интерфейс к функциям splice, push, pop, shift, unshift, keys, values и delete, а также многим другим. Следующий пример демонстрирует использование keys и each: # Инициализировать use Tie::IxHash;tie %food_color, "Tie::IxHas"; $food_color{Banana} = "Yellow"; $food_color{Apple} = "Green"; $food_color{Lemon} = "Yellow"; print "In insertion order, the foods are:\n"; foreach $food (keys %food_color) { print " $food\n"; } print "Still in insertion order, the foods' colors are:\n' while (( $food, $color ) = each %food_color ) { print "$food is colored $color.\n": } In insertion order, the foods are: Banana Apple Lemon Still in insertion order, the foods' colors are: Banana is colored Yellow. Apple is colored Green. Lemon is colored Yellow. > Смотри также ----------------
5.7. Хэши с несколькими ассоциированными значениямиПроблемаТребуется хранить в хэше несколько значений, ассоциированных с одним ключом.РешениеСохраните в хэше ссылку на массив для хранения ассоциированных значений.КомментарийВ хэше могут храниться только скалярные величины. Однако ссылки являются скалярными величинами. Таким образом, проблема решается сохранением в $ХЭШ {$КЛЮЧ} ссылки на массив со значениями, ассоциированными с ключом $КЛЮЧ. Обычные операции с хэшами - вставка, удаление, перебор и проверка существования - переписываются для операций с массивами (push, splice и foreach). Следующий фрагмент реализует простую вставку в хэш. Он обрабатывает выходные данные команды who(i) на компьютере с UNIX и выводит краткий список пользователей с терминалами, на которых они зарегистрированы:%ttys =(); open(WHO, "who|") or die "can't open who: $!"; while ( ($user, $tty) = split; push( @{$ttys{$user}}, $tty ); } foreach $user (sort keys %ttys) { print "$user: @{$ttys{$user}}\n"; } Вся суть этого фрагмента заключена в строке push, где содержится версия $tty{$user} = $tty для многозначного хэша. Все имена терминалов интерполируются в строке print конструкцией @{$ttys{user}}. Если бы, например, нам потребовалось вывести владельца каждого терминала, мы бы организовали перебор анонимного массива: foreach $user (sort keys %ttys) {
Функция exists может иметь два значения: "Существует ли в хэше хотя
бы одно значение для данного ключа?" и "Существует ли данное значение для
данного ключа?" Чтобы реализовать вторую интерпретацию, придется просмотреть
массив в поисках нужной величины. Первая трактовка exists косвенно связана
с функцией delete: если мы можем гарантировать, что ни один анонимный массив
никогда не остается пустым, можно воспользоваться встроенной функцией exists.
Чтобы убедиться, что анонимные массивы не остаются пустыми, их следует
проверять после удаления элемента:
Альтернативная реализация многозначных хэшеи приведена в главе 13 "Классы, объекты и связи", где они реализуются как связанные обычные хэши. > Смотри также -------------------------------
5.8. Инвертирование хэшаПроблемаХэш связывает ключ с ассоциированным значением. У вас имеется хэш и значение, для которого требуется определить ключ.РешениеВоспользуйтесь функцией reverse для создания инвертированного хэша, где ассоциированные значения исходного хэша являются ключами, и наоборот. # %ХЭШ связывает ключи со значениями %ОБРАТНЫЙ = reverse %ХЭШ;КомментарийВ этом решении используется списковая эквивалентность хэшей, о которой упоминалось во введении. В списковом контексте reverse интерпретирует %ХЭШ как список и меняет местами составляющие его элементов. Одно из важнейших свойств списковой интерпретации хэша заключается в том, что элементы списка представляют собой пары "ключ/значение". После инвертирования такого списка первым элементом становится значение, а вторым - ключ. Если интерпретировать такой список как хэш, его значения будут являться ключами исходного хэша, и наоборот. Приведем пример:%surname = ( "Mickey" => "Mantle", "Babe" => "Ruth");
#!/usr/bin/perl -w
Если два ключа исходного хэша имеют одинаковые значения ("Lemon" и "Banana"
в предыдущем примере), то инвертированный хэш будет содержать лишь один
из них (какой именно - зависит от порядка хэширования, так что непредсказуемо).
Дело в том, что хэши в Perl no определению имеют уникальные ключи. Чтобы
инвертировать хэш с повторяющимися значениями, следует воспользоваться
методикой рецепта 5.7 - то есть построить хэш, ассоциированные значения
которого представляют собой списки ключей исходного хэша:
> Смотри также
5.9. Сортировка хэшаПроблемаТребуется работать с элементами хэша в определенном порядке.РешениеВоспользуйтесь функцией keys для построения списка ключей, а затем отсортируйте их в нужном порядке:# %hash - сортируемый хэш
КомментарийХотя хранить элементы хэша в заданном порядке невозможно (без использования модуля Tie:IxHash, упомянутого в рецепте 5.6), перебирать их можно в любом порядке. Существует множество разновидностей одного базового механизма: вы извлекаете ключи, упорядочиваете их функцией sort и обрабатываете элементы в новом порядке. Допускается применение любых хитростей сортировки, упоминавшихся в главе 4 "Массивы". Рассмотрим пару практических примеров. В первом фрагменте sort просто используется для упорядочения ключей по алфавиту:foreach $food (sort keys %food_color) { print "$food is $food_color($food).\n":
foreach $food (sort { $food_color{$a} cmp $food_color{$b} } ) keys
%food_color) {
> Смотри также --------------------------------
5.10. Объединение хэшейПроблемаТребуется создать новый хэш, содержащий элементы двух существующих хэшей.РешениеИнтерпретируйте хэши как списки и объедините их так, как это делается со списками:%merged = (%A, %В); Для экономии памяти можно организовать перебор элементов и построить новый хэш следующим образом: %merged = (); while ( ($k,$v) = each(%A) ) { $merged{$k} = $v; } while ( ($k,$v) = each(%B) ) { $merged{$k} = $v; } КомментарийВ первом варианте, как и в предыдущем рецепте инвертирования хэшей, используется списковая эквивалентность, о которой говорилось во введении. (%A, %В) интерпретируется как список пар "ключ/значение". Когда он присваивается объединенному хэшу %merged, Perl преобразует список пар снова в хэш. Рассмотрим, как эта методика реализуется на практике:# Хэш %food_color определяется во Введении %drink_color = ( Galliano => "yellow", "Mat Tai" => "blue" ); %ingested_colors = (%drink_color, %food_color); Ключи обоих входных хэшей присутствуют в выходном не более одного раза. Если в хэшах найдутся совпадающие ключи, в итоговый хэш включается тот ключ, который встретился последним. Прямое присваивание компактно и наглядно, но при больших размерах хэшей оно приводит к большим расходам памяти. Это связано с тем, что перед выполнением присваивания итоговому хэшу Perl разворачивает оба хэша во временный список. Пошаговое объединение с помощью each, показанное ниже, избавит вас от этих затрат. Заодно вы сможете решить, как поступать с совпадающими ключами. С применением each первый фрагмент записывается следующим образом: # Хэш %food_color определяется во Введении %drlnk_color = ( Galliano => "yellow", "Mat Tai" => "blue" ); %substance_color = (); while (($k, $v) = each %food_color) { $substance_color{$k} = $v; } while (($k, $v) = each %drink_color) { $substance_color{$k} = $v; } Обратите внимание на повторяющийся код присваивания в циклах while.
Проблема решается так:
foreach $substanceref (\%food_color, \%drink_color ) { while (($k,
$v) = each
> Смотри также -----------------------------
|