На этом шаге мы продолжим рассматривать сложные структуры данных.
В качестве следующего примера построим на основе массива @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]; } }
На следующем шаге мы закончим рассматривать сложные структуры данных.