Шаг 65.
Другие структуры данных (окончание). Фрагмент массива

    На этом шаге мы продолжим рассматривать сложные структуры данных.

    В качестве следующего примера построим на основе массива @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];
      }
  }

    На следующем шаге мы закончим рассматривать сложные структуры данных.




Предыдущий шаг Содержание Следующий шаг