Ключевые слова:perl, array, hash, (найти похожие документы)
From: Иван Рак (rak@tut.by)
Newsgroups: http://rak.at.tut.by
Date: Mon, 20 Sep 2004 18:21:07 +0000 (UTC)
Subject: Ссылочные структуры в Perl
Оригинал: http://rak.at.tut.by/perlreftut_ru.html
perlreftut - очень короткое руководство по ссылкам, которое написал Марк
* НАЗВАНИЕ
* ОПИСАНИЕ
* Зачем нужны сложные структуры данных?
* Решение
* Синтаксис
* Создание ссылок
* Правило создания 1
* Использование ссылокs
* Правило использования 1
* Правило использования 2
* Пример
* Правило стрелки
* Решение
* Остальное
* Итого
* Авторы
* Условия распространения
НАЗВАНИЕ
perlreftut - очень короткое руководство по ссылкам, которое написал
Марк
ОПИСАНИЕ
Одно из самых важных новшеств в Perl 5 - это возможность управления
сложными структурами данных, такими, как многомерные массивы и
вложенные хэши. Для этого в Perl 5 введены ссылки, и их использование
позволяет работать с структурированной информацией. Однако, к
сожалению, для этого приходится изучать довольно сложный синтакс, и
основному руководству по ссылкам может быть трудно следовать.
Руководство дает полное описание, и для некоторых людей это проблема,
потому что трудно бывает отличить важное от второстепенного.
К счастью, вам нужно знать только 10% того, что дает руководство,
чтобы иметь 90% возможностей. На этой странице даны именно важные 10%.
Зачем нужны сложные структуры данных?
В Perl 4 постоянно вставала проблема представления данных в виде хэша,
элементами которого являются списки. Конечно, в Perl 4 были хэши, но
его значения должны были быть скалярами, и не могли быть списками.
Для чего может понадобиться хэш списков? Возьмем простой пример: у вас
есть файл с названиями городов и стран:
Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA
и вы хотите получить на выходе следующее: название страны, затем в
алфавитном порядке список городов в этой стране:
Finland: Helsinki.
Germany: Berlin, Frankfurt.
USA: Chicago, New York, Washington.
Естественный путь сделать это - создать хэш, ключи которого - названия
стран. Значением каждого ключа будет список городов этой страны. При
чтении каждой входящей строки надо разделить ее на страну и город,
найти список уже известных городов этой страны, и добавить город в
список. После завершения чтения пройти по хэшу, [как обычно], сортируя
каждый список городов перед печатью.
Если значения хэша не могут быть списками, вы проиграли. В Perl 4
значения хэша не могут быть списками, они могут быть только строками.
Вы проиграли. Возможно, вам придется собирать все города в одну
строку, перед печатью разбивать строку в список, сортировать список, и
превращать его обратно в строку. Это добавит беспорядка и ошибок. И
это утомительно [напряжно. frustrating], потому что в Perl'е уже есть
замечательные списки, которые могут решить проблему... если вы сможете
их использовать.
Решение
Ко времени обкатки Perl 5 мы уже смирились с тем, что что значения
хэша должны быть скалярами. Решение пришло в виде ссылок.
Ссылка - это скалярная величина, которая ссылается на весь массив или
на весь хэш (или еще на что-нибудь). Например, имена - уже известный
вам вид ссылок. Подумайте о президенте США: дурацкий, неудобный мешок
с кровью и и костями. Но чтобы говорить о нем, или представить его в
компьютерной программе, нужна простая удобная строка ``Джордж Буш''.
Ссылки в Perl похожи на имена для списков или хэшей. Это частные,
внутренние имена, поэтому вы можете быть уверены, что они однозначны.
В отличие от слов ``Джордж Буш', ссылка ссылается только на один
объект, и вы всегда знаете, на какой. Если у вас есть ссылка на
массив, вы можете получить весь массив. Если у вас есть сслыка на хэш,
вы можете получить весь хэш. Но ссылка все еще легкая, компактная
скалярная величина.
Не может быть хэша, значения которого - массивы; значения хэша могут
быть только скалярами. Мы опять застряли. Но одна ссылка может
указывать на целый массив, и ссылки - скаляры, так что можно создать
хэш ссылок на массивы, и он будет работать, как хэш массивов, и будет
таким же полезным, как хэш массивов.
Мы вернемся к проблеме городов и стран позже, после того, как мы
рассмотрим синтаксис управления ссылками
Синтаксис
Существует два способа создания ссылок, и два способа их
использования.
Создание ссылок
Правило создания 1
Если поместить перед переменной \, вы получите ссылку на эту
переменную.
$aref = \@array; # $aref содержит ссылку на @array
$href = \%hash; # $href содержит ссылку на %hash
После того, как ссылка сохранена в переменной, ее можно копировать или
просто хранить, как любую другую скалярную величину:
$xy = $aref; # $xy теперь содержит ссылку на @array
$p[3] = $href; # $p[3] содержит ссылку на %hash
$z = $p[3]; # $z содержит ссылку на %hash
В этих примерах показано, как создавать ссылки на именованные объекты.
Может быть, вы захотите создать массив или хэш, у которого нет имени,
по аналогии с возможностью использовать строку "\n" или число 80, не
сохраняя их в именованной переменной.
Правило создания 2
[ ЭЛЕМЕНТЫ ] создает новый анонимный массив, и возвращает ссылку на
этот массив. { ЭЛЕМЕНТЫ } }создает новый анонимный хэш, и возвращает
ссылку на этот хэш.
$aref = [ 1, "foo", undef, 13 ];
# $aref содержит ссылку на массив
$href = { APR => 4, AUG => 8 };
# $href содержит ссылку на хэш
Ссылка, которую вы получаете по правилу 2, такая же, как и та, которая
создана по правилу 1:
# Это:
$aref = [ 1, 2, 3 ];
# Делает то же, что и это:
@array = (1, 2, 3);
$aref = \@array;
Первая строка - сокращение последующих двух строк, кроме того, что она
не создает излишней переменной массива @array
Если вы напишете просто [], вы получите новый пустой анонимный массив.
Если вы напишите просто {}, вы получите новый пустой анонимный хэш.
Использование ссылок
Что можно сделать с созданной переменной? Мы уже видели, что ее можно
хранить, как скалярную величину и получить ее значение. Есть еще два
способа ее использования.
Правило использования 1
Можно использовать имя сслыки на массив в фигурных скобках вместо
имени массива. Например, @{$aref} вместо @array.
Несколько примеров:
Массивы:
@a @{$aref} Массив
reverse @a reverse @{$aref} Массив в обратном порядке
$a[3] ${$aref}[3] Элемент массива
$a[3] = 17; ${$aref}[3] = 17 Присваивание значения элементу массива
В каждой строке два выражения, которые делают то же самое. Слева
варианты, которые работают с массивом @a. Справа - с массивом, на
который сслылаетс я$aref. После нахождения массива оба варианта
выполняют одинаковые операции.
Ссылки на хэш используются точно так же:
%h %{$href} Хэш
keys %h keys %{$href} Получение ключей хэша
$h{'red'} ${$href}{'red'} Элемент хэша
$h{'red'} = 17 ${$href}{'red'} = 17 Присвоение значения элементу
Все, что можно сделать с ссылкой, делается по Правилу использования 1.
Вы просто пишете код на Perl, который делает то, что нужно с обычным
массивом или хэшем, а затем заменяете имя массива или хэша {$ссылкой}.
``Как перебрать элементы массива, если у меня есть ссылка?'' Чтобы
перебрать элементы массива, нужно написать
for my $element (@array) {
...
}
затем замените имя массива @array, ссылкой:
for my $element (@{$aref}) {
...
}
``Как распечатать содержимое хэша, если у меня есть только ссылка?''
Во-первых, пишем код для распечатки хэша:
for my $key (keys %hash) {
print "$key => $hash{$key}\n";
}
Во-вторых, заменяем имя хэша ссылкой:
for my $key (keys %{$href}) {
print "$key => ${$href}{$key}\n";
}
Правило использования 2
Правило использования 1 - это все, что вам нужно, потому что оно
описывает абсолютно все действия с ссылкой. Но чаще всего приходится
извлекать единственный элемент массива или хэша, и запись по Правилу
использования 1 громоздка. Поэтому - несколько сокращений.
${$aref}[3] трудно читать, поэтому можно писать $aref->[3].
${$href}{red} трудно читать, поэтому можно писать $href->{red}.
Если $aref содержит ссылку на массив, то $aref->[3] - четвертый
элемент массива. Не перепутайте его с $aref[3], что есть четвертый
элемент совершенно другого массива, обманчиво названного @aref. $aref
and @aref are unrelated the same way that $item and @item are.
Так же, $href->{'red'} - часть хэша, на который указывает скалярная
переменная $href, возможно даже безымянного. $href{'red'} часть
обманчивого названного %href хэша. Легко забыть вставить ->, и в таком
случае вы получите странные результаты, когда программа будет
извлекать элементы из совершенно неожиданные хэшей и массивов, которые
вы совсем не хотели использовать.
Пример
Небольшой пример того, как все это можно использовать.
Во-первых, вспомним, что [1, 2, 3] создает анонимный массив,
содержащий (1, 2, 3), и возвращает ссылку на этот массив.
Тогда:
@a = ( [1, 2, 3],
[4, 5, 6],
[7, 8, 9]
);
@a - массив из трех элементов, каждый из которых есть ссылка на другой
массив.
$a[1] одна из таких ссылок. Она ссылается на массив, содержащий (4, 5,
6), и, так как это ссылка на массив, по Правилу использования 2 мы
можем написать $a[1]->[2], чтобы получить третий элемени этого
массива. $a[1]->[2] равно 6. $a[0]->[1] равно 2. То, что мы имеем,
похоже на двумерный массив; можно писать $a[СТРОКА]->[СТОЛБЕЦ], чтобы
получть элемент в любой строке и любом столбце массива.
Но эта нотация все еще тяжеловата, поэтому есть еще одно сокращение:
Правило стрелок
Между квадратными скобками стрелка не обязательна.
Вместо $a[1]->[2] мы можем написать $a[1][2]; результат тот же.
Вместо$a[0]->[1] = 23, мы можем написать $a[0][1] = 23; результат тот
же.
Теперь это в самом деле выглядит, как двумерный массив!
Из этого видно, почему важны стрелки. Без них пришлось бы писать
${$a[1]}[2] вместо $a[1][2]. Для трехмерных массивов можено писать
$x[2][3][5]] вместо нечитаемого ${${$x[2]}[3]}[5].
Решение
Теперь - решение поставленнного в начале вопроса о переформатировании
названий городов и стран.
1 my %table;
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
8 foreach $country (sort keys %table) {
9 print "$country: ";
10 my @cities = @{$table{$country}};
11 print join ', ', sort @cities;
12 print ".\n";
13 }
В программе две части: строки 2--7 считывают данные и создают
структуру данных, и строки 8--13 анализируют данные и распечатывают
отчет. Мы получаем хэш %table, ключи которого - названия стран, а
значения - ссылки на массивы названий городов. Эта структура выглядит
примерно так:
%table
+-------+---+
| | | +-----------+--------+
|Germany| *---->| Frankfurt | Berlin |
| | | +-----------+--------+
+-------+---+
| | | +----------+
|Finland| *---->| Helsinki |
| | | +----------+
+-------+---+
| | | +---------+------------+----------+
| USA | *---->| Chicago | Washington | New York |
| | | +---------+------------+----------+
+-------+---+
Рассмотрим сначала вывод. Предположим, что у нас уже есть эта
структура, как ее распечатать?
8 foreach $country (sort keys %table) {
9 print "$country: ";
10 my @cities = @{$table{$country}};
11 print join ', ', sort @cities;
12 print ".\n";
13 }
%table - обычный хэш, и мы получаем список его ключей, сортируем
ключи, и перебираем ключи как обычно. Единственное использование
ссылок - в строке 10. $table{$country} ищет ключ $country в хэше, и
получает значение - ссылку на массив городов этой страны. Правило
использования 1 говорит, что мы можем получить массив, написав
@{$table{$country}}. Строка 10 похожа на
@cities = @array;
кроме того, что имя array заменено ссылкой {$table{$country}}. Знак @
позволяет Perl'у получить весь массив. Получив список городов, мы
сортируем его, соединяем, и распечатываем, как обычно.
Строки 2-7 отвечают в первую очередь за построение структуры. Вот они
еще раз:
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
В строках 2-4 мы получаем название города и страны. Строка 5 проверяет
наличие такой страны в ключах хэша. Если ее нет, программа использует
запись [] (Правило создания 2), чтобы получить новый пустой анонимный
массив городов, и установить ссылку на него в качестве значения для
соответствующего ключа хэша.
Строка 6 добавляет название города в соответствующий массив.
$table{$country}
push @array, $city;
кроме того, что имя array заменено ссылкой {$table{$country}}. Команда
push [TODO: link] добавляет название города в конец указываемого
массива.
Есть один тонкий момент, который я пропустил. Строка 5 необязательна,
поэтому ее можно выбросить
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 #### $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
Если в %table уже есть запись для текущей страны $country, тогда
ничего не изменится. Строка 6 найдет значение $table{$country},
которое указывает на массив, и добавит город $city в массив. Но что
произойдет, если $country содержит ключ, которого еще нет в %table,
например, Greece?
Это Perl, поэтому все пройдет так, как надо. Если вы хотите добавить
Athens в несуществующий массив, то будет правильно [TODO:] создать
новый пустой анонимный массив, включить его в %table, и добавить в
него Athens. Это называется 'autovivification' [TODO: автооживление]
-- автоматическое оживление вещей. Perl видит, что такого ключа не
существует в хэше, так что он создает новый хэш автоматически. Perl
видит, что вы хотите использовать значение ключа как массив, поэтому
он создает новый пустой массив, и автоматически включает сслыку на
него в хэш. И как обычно, Perl делает массив на один элемент длиннее,
чтобы сохранить в нем новое название города.
Остальное
Я обещал дать 90% возможностей, используя 10% подробностей, и это
значит, что 90% подробностей я упустил. Теперь, после обзора самого
важного, вам будет легче читать the perlref manpage[TODO: link], в
котором даны 100% информации.
Несколько важных мест the perlref manpage [TODO: link]:
* Можно создавать ссылки на все объекты, включая скаляры, функции, и
другие ссылки.
* По Правилу использования 1 вы можете опустить фигурные скобки
всегда, the thing inside them is an atomic scalar variable like
$aref. [TODO:] Например, @$aref - то же, что @{$aref}, а $$aref[1]
- то же, что ${$aref}[1]. Пока вы не освоитесь, вам лучше
привыкнуть всегда писать фигурные скобки.
* Так массив не скопировать:
$aref2 = $aref1;
Вы получите два указателя на один и тот же массив. Если вы
измените $aref1->[23], а затем посмотрите на $aref2->[23], вы
увидите изменения.
Чтобы скопировать массив, используйте
$aref2 = [@{$aref1}];
Здесь используется запись [...] для того, чтобы создать новый
анонимный массив, и $aref2 получит значение ссылки на новый
массив. Новый массив инициализируется содержимым массива, на
который указывает $aref1.
Похожим способом можно скопировать анонимный хэш:
$href2 = {%{$href1}};
* Чтобы узнать, содержит ли переменная ссылку, используйте функцию
ref [TODO: link]. Она возвращает истину, если аргумент есть
ссылка. На самом деле, даже лучше: она возвращает code>HASH для
ссылок на хэш, и ARRAY для ссылок на массив.
* Если вы попробуете использовать ссылку как строку, вы получите
строки вроде таких:
ARRAY(0x80f5dec) or HASH(0x826afc0)
Если вы увидите такие строки - знайте: вы по ошибке распечатали
ссылку.
Побочный эффект такого представления в том, что вы можете
использовать eq для проверки того, на один ли и тот же объект
указывают ссылки. (Но обычно лучше использовать ==, потому что так
работает намного быстрее).
* Вы можете использовать строку, как если бы это была ссылки. Если
использовать строку "foo" как ссылку на массив, это будет
считаться ссылкой на массив @foo. Это так называемая мягкая
ссылка, или символическая ссылка [TODO:]. Объявление use strict
'refs' отключает эту возможность, которая может вызвать массу
проблем, если используется по ошибке.
Возможно, вместо the perlref manpage [TODO: link], вам стоит
сначала прочесть the perllol manpage [TODO:], в котором подробно
обсуждаются списки списков и многомерные массивы. После этого
ознакомьтесь с the perldsc manpage [TODO:link] - Поваренной Книгой
Структур Данных (Data Structure Cookbook), в которой даны рецепты
использования и распечатки массивов хэшей, хэшей массивов, и тому
подобного.
Итого
Всем нужны сложные структуры данных, и Perl позволяет создавать их с
помощью ссылок. Есть четыре правила управления ссылками: два правила
создания и два правила использования. Когда вы усвоите эти правила, вы
сможете делать почти все важное, что можно сделать с ссылками.
Авторы.
Автор: Mark Jason Dominus, Plover Systems (mjd-perl-ref+@plover.com)
Впервые статья появилась в The Perl Journal (http://www.tpj.com/),
том 3, #2. Перепечатывается с разрешения [это про оригинал]
Первоначально статья называлась ``Пойми ссылки сегодня'' (Understand
References Today).
Перевод: Иван Рак (rak@tut.by)
Условия распространения
Copyright 1998 The Perl Journal.
Документ бесплатный; вы можете распространять и/или изменять его на
тех же условиях, что и сам Perl.
Независимо от распространения, все примеры кода в этом файле являются
публичным достоянием. Разрешается и поощряется использование этого
кода в ваших собственных программах для развлечения или для получения
прибыли. Простой комментарий с указанием авторства приятен, но не
обязателен.
perlreftut - очень короткое руководство по ссылкам, которое написал Марк