Spec-Zone.ru › Perl 5.36

perlreftut

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ОПИСАНИЕ
  • Кому нужны сложные структуры данных?
  • Решение
  • Синтаксис
    • Создание ссылок
      • Правило создания 1
      • Правило создания 2
    • Использование ссылок
      • Правило использования 1
      • Правило использования 2
    • Пример
    • Правило стрелки
  • Решение
  • Остальное
  • Заключение
  • Авторы
    • Условия распространения

НАЗВАНИЕ

perlreftut - Краткий учебник Марка о ссылках

ОПИСАНИЕ

Одной из самых важных новых функций в Perl 5 была возможность управления сложными структурами данных, такими как многомерные массивы и вложенные хеши. Для этого в Perl 5 была добавлена функция ссылок, и использование ссылок является ключом к управлению сложными, структурированными данными в Perl. К сожалению, здесь много странного синтаксиса, и основную страницу руководства может быть сложно понять. Руководство довольно полное, и иногда люди считают это проблемой, потому что трудно понять, что важно, а что нет.

К счастью, вам нужно знать только 10% того, что написано на главной странице, чтобы получить 90% преимуществ. Эта страница покажет вам эти 10%.

Кому нужны сложные структуры данных?

Постоянно возникает проблема необходимости хеша, значениями которого являются списки. В Perl, конечно, есть хеши, но значения должны быть скалярами; они не могут быть списками.

Зачем вам нужен хеш со списками значений? Рассмотрим простой пример: у вас есть файл с названиями городов и стран, например:

Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA

и вы хотите получить вывод, похожий на этот, где каждая страна упоминается один раз, а затем идёт алфавитный список городов этой страны:

Finland: Helsinki.
Germany: Berlin, Frankfurt.
USA:  Chicago, New York, Washington.

Естественный способ сделать это — использовать хеш, ключами которого являются названия стран. С каждым ключом страны связан список городов этой страны. Каждый раз при чтении строки входного файла, разбейте её на страну и город, найдите список городов, уже известных по этой стране, и добавьте новый город в этот список. После завершения чтения входных данных, как обычно, проитерируйте по хешу, отсортировав каждый список городов перед его выводом.

Если значения хеша не могли быть списками, вы бы потеряли гибкость. Вам, вероятно, пришлось бы объединить все города в одну строку каким-то образом, а затем, когда пришло бы время вывести результат, вам пришлось бы разбить строку на список, отсортировать список и преобразовать его обратно в строку. Это грязно и подвержено ошибкам. И это разочаровывает, потому что Perl уже имеет идеально подходящие списки, которые бы решили проблему, если бы вы только могли их использовать.

Решение

К тому времени, когда появился Perl 5, у нас уже была проблема: значения хеша должны были быть скалярами. Решение этой проблемы — ссылки.

Ссылка — это скалярное значение, которое ссылается на целый массив или целый хеш (или на почти что угодно другое). Имена — это один из типов ссылок, с которыми вы уже знакомы. Каждый человек — это запутанное, неудобное скопление клеток. Но чтобы сослаться на конкретного человека, например, на первого программиста, не нужно описывать каждую их клетку; вам нужно только простое, удобное скалярное строковое значение "Ada Lovelace".

Ссылки в Perl — это как имена для массивов и хешей. Это внутренние, приватные имена Perl, поэтому вы можете быть уверены в их уникальности. В отличие от имени человека, ссылка указывает только на одну вещь, и вы всегда знаете, на что она указывает. Если у вас есть ссылка на массив, вы можете восстановить весь массив по ней. Если у вас есть ссылка на хеш, вы можете восстановить весь хеш. Но ссылка всё равно является удобным, компактным скалярным значением.

Вы не можете иметь хеш, значениями которого являются массивы; значения хеша могут быть только скалярами. Мы застряли с этим. Но одна ссылка может ссылаться на целый массив, а ссылки являются скалярами, поэтому вы можете иметь хеш ссылок на массивы, и это будет работать почти так же, как хеш массивов, и это будет так же полезно, как хеш массивов.

Мы вернёмся к этой задаче с городами и странами позже, после того, как увидим некоторый синтаксис для управления ссылками.

Синтаксис

Существует только два способа создания ссылки и только два способа её использования после создания.

Создание ссылок

Правило создания 1

Если вы поставите \ перед переменной, вы получите ссылку на эту переменную.

$aref = \@array;         # $aref now holds a reference to @array
$href = \%hash;          # $href now holds a reference to %hash
$sref = \$scalar;        # $sref now holds a reference to $scalar

После того, как ссылка будет сохранена в переменной, такой как $aref или $href, вы можете скопировать её или сохранить её так же, как любое другое скалярное значение:

$xy = $aref;             # $xy now holds a reference to @array
$p[3] = $href;           # $p[3] now holds a reference to %hash
$z = $p[3];              # $z now holds a reference to %hash

Эти примеры показывают, как создать ссылки на переменные с именами. Иногда вы хотите создать массив или хеш, у которого нет имени. Это аналогично тому, как вы хотите использовать строку "\n" или число 80 без предварительного сохранения их в именованной переменной.

Правило создания 2

[ ITEMS ] создаёт новый анонимный массив и возвращает ссылку на этот массив. { ITEMS } создаёт новый анонимный хеш и возвращает ссылку на этот хеш.

$aref = [ 1, "foo", undef, 13 ];
# $aref now holds a reference to an array

$href = { APR => 4, AUG => 8 };
# $href now holds a reference to a hash

Ссылки, полученные по правилу 2, являются тем же типом ссылок, что и полученные по правилу 1:

# This:
$aref = [ 1, 2, 3 ];

# Does the same as this:
@array = (1, 2, 3);
$aref = \@array;

Первая строка — это сокращение следующих двух строк, за исключением того, что она не создаёт лишнюю переменную массива @array.

Если вы напишете просто [], вы получите новый пустой анонимный массив. Если вы напишете просто {}, вы получите новый пустой анонимный хеш.

Использование ссылок

Что вы можете сделать со ссылкой, после того как она у вас есть? Это скалярное значение, и мы видели, что вы можете сохранить его как скаляр и получить его обратно так же, как любой другой скаляр. Существует ещё два способа её использования:

Правило использования 1

Вы всегда можете использовать ссылку на массив в фигурных скобках вместо имени массива. Например, @{$aref} вместо @array.

Вот некоторые примеры этого:

Массивы:

@a              @{$aref}                An array
reverse @a      reverse @{$aref}        Reverse the array
$a[3]           ${$aref}[3]             An element of the array
$a[3] = 17;     ${$aref}[3] = 17        Assigning an element

В каждой строке приведены два выражения, которые делают одно и то же. Левые версии работают с массивом @a. Правые версии работают с массивом, на который ссылается $aref. Как только они найдут массив, с которым работают, обе версии делают с ним то же самое.

Использование ссылки на хеш точно такое же:

%h              %{$href}              A hash
keys %h         keys %{$href}         Get the keys from the hash
$h{'red'}       ${$href}{'red'}       An element of the hash
$h{'red'} = 17  ${$href}{'red'} = 17  Assigning an element

Что бы вы ни хотели сделать со ссылкой, Правило использования 1 показывает, как это сделать. Вы просто записываете код Perl, который вы бы написали для выполнения того же действия с обычным массивом или хешем, а затем заменяете имя массива или хеша на {$reference}. "Как я могу пройти по массиву, если у меня есть только ссылка?" Чтобы пройти по массиву, вы бы написали

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 и @aref не связаны так же, как $item и @item.

Аналогично, $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[ROW]->[COLUMN] для получения или установки элемента в любом ряду и любой колонке массива.

Нотация всё ещё немного громоздка, поэтому есть ещё одно сокращение:

Правило стрелки

Между двумя индексами стрелка является необязательной.

Вместо $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   for my $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   for my $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} теперь содержит ссылку на массив городов, увиденных в этой стране до сих пор. Строка 6 точно такая же, как

push @array, $city;

за исключением того, что имя array было заменено на ссылку {$table{$country}}. push добавляет имя города в конец указанного массива.

Есть один важный момент, который я пропустил. Строка 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 содержит ключ, скажем Greece, который ещё не находится в %table?

Это Perl, поэтому он делает абсолютно правильное действие. Он видит, что вы хотите добавить Athens в массив, которого нет, поэтому он полезно создаёт новый пустой анонимный массив, устанавливает ссылку на него в %table, а затем добавляет Athens в него. Это называется автовивификация — придание вещам жизни автоматически. Perl увидел, что ключа нет в хеш-таблице, поэтому автоматически создал новую запись в хеш-таблице. Perl увидел, что вы хотите использовать значение хеш-таблицы как массив, поэтому автоматически создал новый пустой массив и установил ссылку на него в хеш-таблице. И, как обычно, Perl увеличил длину массива на один элемент, чтобы вместить новое имя города.

Остальное

Я обещал дать вам 90% выгоды с 10% деталей, а это означает, что я оставил 90% деталей. Теперь, когда у вас есть общее представление о важных частях, вам должно быть легче прочитать страницу руководства perlref, в которой обсуждаются все 100% деталей.

Некоторые моменты из perlref:

  • Вы можете создавать ссылки на всё, включая скаляры, функции и другие ссылки.

  • В Правиле использования 1 вы можете опустить фигурные скобки, когда содержимое внутри них — это атомарная скалярная переменная, например, $aref. Например, @$aref то же самое, что и @{$aref}, а $$aref[1] то же самое, что и ${$aref}[1]. Если вы только начинаете, вам может потребоваться принять привычку всегда включать фигурные скобки.

  • Это не копирует основной массив:

    $aref2 = $aref1;

    Вы получаете две ссылки на один и тот же массив. Если вы измените $aref1->[23] и затем посмотрите на $aref2->[23], вы увидите изменение.

    Чтобы скопировать массив, используйте

    $aref2 = [@{$aref1}];

    Это использует обозначение [...] для создания нового анонимного массива, и $aref2 получает ссылку на новый массив. Новый массив инициализируется содержимым массива, на который ссылается $aref1.

    Аналогично, для копирования анонимной хеш-таблицы можно использовать

    $href2 = {%{$href1}};
  • Чтобы проверить, содержит ли переменная ссылку, используйте функцию ref. Она возвращает true, если её аргумент является ссылкой. На самом деле, она немного лучше: она возвращает HASH для ссылок на хеш-таблицы и ARRAY для ссылок на массивы.

  • Если вы попытаетесь использовать ссылку как строку, вы получите строки, подобные

    ARRAY(0x80f5dec)   or    HASH(0x826afc0)

    Если вы когда-либо увидите строку, похожую на эту, вы будете знать, что вы случайно вывели ссылку.

    Побочным эффектом этого представления является то, что вы можете использовать eq, чтобы проверить, ссылаются ли две ссылки на одну и ту же вещь. (Но вы обычно должны использовать ==, потому что это намного быстрее.)

  • Вы можете использовать строку как ссылку. Если вы используете строку "foo" как ссылку на массив, она воспринимается как ссылка на массив @foo. Это называется символьной ссылкой. Декларация use strict 'refs' отключает эту функцию, что может вызвать всяческие проблемы, если вы случайно её используете.

Вам может быть предпочтительнее перейти к perllol вместо perlref; там подробно рассматриваются списки списков и многомерные массивы. После этого вы должны перейти к perldsc; это кулинарная книга по структурам данных, которая показывает рецепты использования и вывода массивов хешей, хешей массивов и других типов данных.

Заключение

Всем нужны сложные структуры данных, и в Perl их получают с помощью ссылок. Есть четыре важных правила для управления ссылками: два для создания ссылок и два для их использования. Как только вы узнаете эти правила, вы сможете выполнить большинство важных задач, связанных со ссылками.

Благодарности

Автор: Марк Джейсон Доминус, Plover Systems (mjd-perl-ref+@plover.com)

Эта статья первоначально появилась в журнале The Perl Journal ( http://www.tpj.com/ ) том 3, #2. Перепечатано с разрешения.

Оригинальное название было Understand References Today.

Условия распространения

Авторское право 1998 The Perl Journal.

Эта документация бесплатна; вы можете перераспределять и/или изменять её в соответствии с теми же условиями, что и Perl сам по себе.

Независимо от его распространения, все примеры кода в этих файлах объявляются всеобщим достоянием. Вам разрешено и поощряется использовать этот код в своих собственных программах для развлечения или с выгодой, как вам заблагорассудится. Простой комментарий в коде, выражающий признательность, был бы вежливым, но не является обязательным.

© 1993–2021 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.36.0/perlreftut

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API