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

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

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

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


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

for $i (0..11) 
{
  for $j (0..$#{$calendar[$i]})   
  {
    push  @{$months[$i] {$calendar [$i] [$j] } },   $j+1;
  }
}


    Замечание. Функция
push @array, list

помещает список list в конец массива @array.


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

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

for $i (0..11) 
{
  print "месяц № ", $i+1, "\n";
  for $DayName (keys % {$months [$i] } ) 
  {
    print "  ${DayName}:    @{$months [$i] {$DayName} } \n";
  }
}

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


Рис.2. Результат работы приложения

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

    Приведем полный текст примера:

$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;
  }
}
for $i (0..11) 
{
  print "месяц №", $i+1, "\n";
  for $DayName (keys % {$months [$i] } ) 
  {
    print "  ${DayName}:    @{$months [$i] {$DayName} } \n";
  }
}

exit;

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]; }
}
Текст этого примера можно взять здесь.

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

  sort   SOBNAME   LIST

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

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


Рис.3. Результат работы приложения

    Текст приложения:

$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;
  }
}
for $i (0..11) 
{
  print "месяц №", $i+1, "\n";
  for $DayName (sort WeekOrder keys %{$months [$i]} ) 
  { 
      print "  $DayName  @{$months [$i] {$DayName} } \n"; 
  }
}

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]; }
}
Текст этого примера можно взять здесь.

    Как видно из рисунка 3, вывод дат является неупорядоченным. Изменим текст последнего примера, добавив в него формат вывода и изменив блок вывода.

    Анализ вывода дат показывает, что колонок может быть максимум пять. поэтому можно использовать следующий формат, задаваемый по умолчанию:

format  STDOUT  =
@<<<<<<<<<<<<<<<<<<<  @>>  @>>  @>>  @>>  @>> 
$DayName, @{$months [$i] {$DayName} }
.

    Цикл вывода изменится следующим образом:

for $i (0..11) 
{
  print "месяц №", $i+1, "\n";
  for $DayName (sort WeekOrder keys %{$months [$i]} ) 
  { 
      write; # Форматный вывод.
  }
}

    Результат работы измененного приложения изображен на рисунке 4:


Рис.4. Результат работы приложения

    Текст измененного приложения:

$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;
  }
}
for $i (0..11) 
{
  print "месяц №", $i+1, "\n";
  for $DayName (sort WeekOrder keys %{$months [$i]} ) 
  { 
      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} }
.
Текст этого примера можно взять здесь.

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




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