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

Рис.1. Ассоциативный массив %months со сложной внутренней структурой
При построении хеш-массива %months воспользуемся вспомогательным хеш-массивом %OrderedMonths, который будем использовать для задания порядка сортировки:
%OrderedMonths = ("January (Январь)"=>0,
"February (Февраль)"=>1,
"March (Март)"=>2,
"April (Апрель)"=>3,
"Маy (Май)"=>4,
"June (Июнь)"=>5,
"July (Июль)"=>6,
"August (Август)"=>7,
"September (Сентябрь)"=>8,
"October (Октябрь)"=>9,
"November (Ноябрь)"=>10,
"December (Декабрь)"=>11);
# Формирование структуры.
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}})
{
write; # Форматный вывод.
}
}
В результате выполнения примера будет распечатан календарь на 2006 год в виде:

Рис.2. Результат работы приложения
Полный текст приложения:
$f = GetDay(2006,1,1);
for $i (1,3..12)
{
for $j (1..30)
{
$calendar[$i-1][$j-1] = &$f($i,$j);
for $i (1,3,5,7,8,10,12)
{
$calendar[$i-1][30] = &$f($i, 31);
}
for $j (1..28) {
$calendar[1][$j-1] = &$f(2, $j); }
}
}
# Если год високосный, то добавляется еще один элемент массива.
# $calendar[1][28] = &$f(2,29);
for $i (0..11)
{
for $j (0..$#{$calendar[$i]})
{
push @{$months[$i] {$calendar [$i] [$j] } }, $j+1;
}
}
%OrderedMonths = ("January (Январь)"=>0,
"February (Февраль)"=>1,
"March (Март)"=>2,
"April (Апрель)"=>3,
"Маy (Май)"=>4,
"June (Июнь)"=>5,
"July (Июль)"=>6,
"August (Август)"=>7,
"September (Сентябрь)"=>8,
"October (Октябрь)"=>9,
"November (Ноябрь)"=>10,
"December (Декабрь)"=>11);
# Формирование структуры.
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}})
{
write; # Форматный вывод.
}
}
exit;
sub WeekOrder
{
my %week= ("Monday (Понедельник)"=>0 ,
"Tuesday (Вторник)"=>1,
"Wednesday (Среда)"=>2 ,
"Thursday (Четверг)"=>3,
"Friday (Пятница)"=>4,
"Saturday (Суббота)"=>5,
"Sunday (Воскресенье)"=>6);
$week{$a}<=>$week{$b};
}
sub GetDay {
my $year = shift;
my @days = (0,31,59,90,120,151,181,212,243,273,304,334);
my @week = ("Monday (Понедельник)", "Tuesday (Вторник)", "Wednesday (Среда)",
"Thursday (Четверг)", "Friday (Пятница)", "Saturday (Суббота)",
"Sunday (Воскресенье)");
my $previous_years_days = ($year - 1)*365 + int ( ($year-1) /4) -
int ( ($year-1)/100) + int ( ($year-1)/400);
return sub { my ($month, $day) =@_;
my $n = $previous_years_days + $days[$month-1] + $day - 1;
$n++ if ($year%4 == 0 and $year%100 != 0 or $year%400 == 0 and
$month > 2);
return $week[$n%7]; }
}
format STDOUT =
@<<<<<<<<<<<<<<<<<<< @>> @>> @>> @>> @>>
$DayName, @{$months [$i] {$DayName} }
.
Рассмотренные примеры иллюстрируют подход, используемый в Perl для построения сложных структур данных. Можно сравнить возможности, предоставляемые языком Perl, с возможностями распространенных языков программирования, таких как Pascal или С. Любая сложная структура в Perl на "верхнем" уровне представляет собой массив или ассоциативный массив, в который вложены ссылки на массивы или хеш-массивы следующего уровня и т.д. В этой иерархии ссылки на массивы и хеш-массивы могут чередоваться в произвольном порядке. При помощи такого подхода средствами Perl можно представить любую структуру С или запись языка Pascal. Perl позволяет создавать структуры, которые в других языках создать трудно или невозможно, например, структуру, эквивалентную массиву, состоящему из элементов разных типов:
@array = {1, 2, 3, {"оnе"=>1, "two"=>2}, \&func, 4, 5};
Для доступа к элементам массива мы имеем специальную нотацию, состоящую из префикса $, имени массива и индекса элемента в квадратных скобках, например, $аrrау[7]. Если здесь вместо индекса поместить список индексов, а префикс $ заменить префиксом @, то такая запись будет обозначать фрагмент массива, состоящий из элементов с индексами из заданного списка. Подобную нотацию можно использовать в выражениях, например:
@subarrayl = @array[7..12]; @subarray2 = @array[3,5,7];
Массив @subarrayl является фрагментом массива @array, состоящим из элементов со значениями индекса от 7 до 12. Массив @subarray2 является фрагментом массива @array, состоящим из элементов со значениями индекса 3, 5 и 7. В первом случае список индексов задан при помощи операции "диапазон", во втором случае - перечислением.
Для многомерного массива понятие "фрагмент" обобщается и означает подмножество элементов, получающееся, если для некоторых индексов из диапазона их изменения выделить список допустимых значений. Для выделения одномерных фрагментов можно воспользоваться приведенной выше нотацией. Например, для выделения из массива @calendar фрагмента, содержащего календарь на первую неделю апреля, можно использовать запись:
@april_first_week = @{$calendar[3]}[0..6];
Если выделяемый фрагмент является многомерным, то для его обозначения специальной нотации не существует. В этом случае следует сформировать новый массив, являющийся фрагментом исходного массива. Например, для выделения из массива @calendar календаря на первый квартал можно воспользоваться циклом:
for $i (0..2) {
for $j (0..$#{$calendar[$i]})
{
$quarter1[$i][$j] = $calendar[$i][$j];
}
}
На следующем шаге мы закончим рассматривать сложные структуры данных.