Иллюстрированный самоучитель по Perl

       

Другие структуры данных


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

Новая структура представляет собой массив @months, состоящий из 12 элементов по числу месяцев в году. Каждый элемент содержит ссылку на анонимный хеш-массив. Каждый вложенный хеш-массив содержит набор ключей, имеющих имена, совпадающие с английскими названиями дней недели: "Monday", "Tuesday" и т. д. Каждому ключу соответствует значение, являющееся, в свою очередь, ссылкой на анонимный массив, содержащий все числа данного месяца, приходящиеся на день недели, соответствующий ключу: все понедельники, все вторники и т. д. Структура массива @months представлена на рис. 9.2.

Рис 9.2. Структура массива @months

for $i (0..11) { . for $j (0..$#{$calendar[$i.]}> {

push @{$months[$i]{$calendar[$i][$j]}}, $j+l; } };

Замечание
Функция push @array, list помещает список list в конец массива garray.



Первым аргументом встроенной функции push является массив, в который попадают все дни (i+l)-ro месяца, приходящиеся на один и тот же день недели: все понедельники, все вторники и т. д. На этот массив указывает ссылка $months[$i] {"key"}, где ключ "key" принимает значения "Monday", "Tuesday" и т. д. Для обращения к самому массиву ссылку следует разымено-вать, заключив в фигурные скобки: @{$months[$i] ("key"}}. Если вместо ключа "key" подставить нужное значение из $caiendar[$i] [$j], то получим аргумент функции push.

Вновь сформированную структуру удобно использовать для вывода календаря в традиционном виде. Последовательность операторов




for $i (0..11) {

print "month # ", $i+l, "\n";

for $DayName (keys %{$months[$i]}) {

print " ${DayName}: @{$months[$i]{$DayName}}\n";

} };

распечатает календарь в виде

month #1

Monday 3 10 17 24 31

Thursday 6 13 20 27

Wednesday 5 12 19 26

Sunday 2 9 16 23 30

Saturday 1 8 15 22 29

Friday 7 14 21 28

Tuesday 4 11 18 25

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

Для вывода ключей в порядке следования дней недели воспользуемся встроенной функцией сортировки

sort SUBNAME LIST



Замечание
Функция sort () сортирует список LIST и возвращает отсортированный список значений. По умолчанию используется обычный лексикографический (словарный) порядок сортировки. Его можно изменить при помощи аргумента SUBNAME, представляющего собой имя подпрограммы. Подпрограмма SUBNAME возвращает целое число, определяющее порядок следования элементов списка. Любая процедура сортировки состоит из последовательности сравнений двух величин. Для того чтобы правильно задать порядок сортировки, надо представить себе SUBNAME как функцию двух аргументов. В данном случае аргументы в подпрограмму SUBNAME передаются не общим для Perl способом - через массив @_, а через переменные $а и $ь, обозначающие внутри подпрограммы соответственно первый и второй аргумент. Подпрограмму SUBNAME надо составить таким образом, чтобы она возвращала положительное целое, нуль, отрицательное целое, когда при сравнении аргумент $а назначается меньшим аргумента $ь, равным аргументу $b, большим аргумента $ь соответственно. Для этого внутри подпрограммы удобно использовать операции числового (<=>) и строкового (стр) сравнения, возвращающие значения -1,0, 1, если первый аргумент соответственно меньше второго, равен второму, больше второго.
Вместо имени подпрограммы в качестве аргумента SUBNAME может использоваться блок, определяющий пррядок сортировки.



Зададим функцию weekOrder, определяющую порядок сортировки

sub WeekOrder {

my %week=("Monday"=>0,

"Tuesday"=>1,

"Wednesday"=>2,

"Thursday"=>3,

"Friday"=>4,

"Saturday"=>5,

"Sunday"=>6) ; $week{$a}<=>$week{$b} };

Используя функцию sort () с заданным порядком сортировки

for $i (0..11) {

print "month # ", $1+1, "\n";

for $DayName (sort WeekOrder keys %{$months[$i]}) { print " $DayName @{$months[$i]{$DayName}}\n";

} • ' ' };

получим структурированный вывод календаря в виде, упорядоченном по месяцам и дням недели:

month f 1

Monday 3 10 17 24 31

Tuesday 4 11 18 25

Wednesday 5 12 19 26

Thursday 6 13 20 27

Friday 7 14 21 28

Saturday 1 8 15 22 29

Sunday 2 9 16 23 30

В качестве следующего примера построим на основе массива gmonths новую структуру, которую можно было бы назвать "хеш-массив хеш-массивов массивов", если бы такое название имело право на существование. В действительности, все просто. Речь идет о том, чтобы заменить в массиве @months числовые индексы ключами, совпадающими с названиями месяцев, и таким образом получить ассоциативный массив %months со сложной внутренней структурой (см. рис. 9.3).

Рис 9.3. Ассоциативный массив %months со сложной внутренней структурой

При построении хеш-массива %months воспользуемся вспомогательным хеш-массивом %OrderedMonths, который будем использовать для задания порядка сортировки:

# вспомогательный массив %OrderedMonths %OrderedMonths =( "January"=>0,

"February"=>l,

"March"=>2,

"April"=>3,

"Мау"=>4, "June"=>5, "July"=>6, "August"=>7, "September"=>8, "October"=>9, "November"=>10, "December"=>ll ); # формирование структуры for $month (sort {$OrderedMonths{$a}<=>$OrderedMonths{$b}}

keys %OrderedMonths) { $i = $OrderedMonths{$month}; $months{$month}=$months[$ i];' };



# Вывод элементов хеш-массива %months for $month (sort {$OrderedMonths{$a}<=>$OrderedMonths{$b}}

keys %OrderedMonths) { print "$month\n"; $i = $OrderedMonths{$month); for $DayName (sort WeekOrder keys %{$months{$month}}) {

print " $DayName @{$months[$i]{$DayName}}\n"; } };

В результате выполнения примера 9. 3 будет распечатан календарь на 2000 год в виде:

January

Monday 3 10 17 24 31

Tuesday 4 11 18 25

Wednesday 5 12 19.26

Thursday 6 13 20 27

Friday 7 14 21 28

Saturday 1 8 15 22 29

Sunday 2 9 16 23 30

Рассмотренные примеры иллюстрируют подход, используемый в Perl для построения сложных структур данных. Читатель может сравнить возможности, предоставляемые языком Perl, с возможностями распространенных языков программирования, таких как Pascal или С. Любая сложная структура в Perl на "верхнем" уровне представляет собой массив или ассоциативный массив, в который вложены ссылки на массивы или хеш-массивы следующего уровня и т. д. В этой иерархии ссылки на массивы и хеш-массивы могут чередоваться в произвольном порядке. При помощи такого подхода средствами Perl можно представить любую структуру С или запись языка Pascal. Perl позволяет с легкостью создавать структуры, которые в других языках создать трудно или невозможно, например, структуру, эквивалентную массиву, состоящему из элементов разных типов:

@аггау = (1, 2 ,3, ("опе"=>1, "two"=>2}, \sfunc, 4, 5};

Читатель может поупражняться в построении таких структур и открыть для себя новые нюансы применения этого гибкого и мощного подхода.

В заключение несколько слов о фрагментах массивов. Для доступа к элементам массива мы имеем специальную нотацию, состоящую из префикса $, имени массива и индекса элемента в квадратных скобках, например, $аггау[7]. Если здесь вместо индекса поместить список индексов, а префикс $ заменить префиксом @, то такая запись будет обозначать фрагмент массива, состоящий из элементов с индексами из заданного списка. Подобную нотацию можно использовать в выражениях, например,



Ssubarrayl = @array[7..12]; @subarray2 = @array[3,5,7];

Массив @ subarrayi является фрагментом массива garray, состоящим из элементов со значениями индекса от 7 до 12. Массив @subarray2 является фрагментом массива @аггау, состоящим из элементов со значениями индекса 3, 5 и 7. В первом случае список индексов задан при помощи операции "диапазон", во втором случае - перечислением.

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

@april_first_week = @{'$calendar [3] } [0. . 6];

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

for $i (0..2) { .

for $j (0..$#{$calendar[$i]}) {

$quarter.l[$i] [$j] = $ calendar [$i] [$j ] ; } ' };


Содержание раздела