Perl: создание массивов внутри сложного хеша

Стремясь сделать свои данные более доступными, я хочу хранить табличные данные в сложном хеш-коде. Я пытаюсь вырастить «HoHoHoA», пока сценарий перебирает мои данные. Согласно рекомендациям в perldsc:

push @ { $hash{$column[$i]}{$date}{$hour} }, $data[$i];

Скрипт компилируется и запускается без проблем, но не добавляет никаких данных в хеш:

print $hash{"Frequency Min"}{"09/07/08"}{"15"}; 

ничего не возвращает, даже если ключи должны существовать. Использование хэша «существует» показывает, что его не существует.

Файл данных, который я читаю, выглядит так:

DATE       TIME     COLUMN1 COLUMN2 COLUMN3...    
09/06/2008 06:12:56 56.23   54.23   56.35...
09/06/2008 06:42:56 56.73   55.28   54.52...
09/06/2008 07:12:56 57.31   56.79   56.41...
09/06/2008 07:42:56 58.24   57.30   58.86...
.
.
.

Я хочу сгруппировать значения каждого столбца в массиве для любой заданной даты и часа, отсюда и три хэша для {COLUMN}, {DATE} и {HOUR}.

Полученная структура будет выглядеть так:

%monthData = (
               "COLUMN1" => {
                                    "09/06/2008" => {
                                                      "06" => [56.23,56.73...],
                                                      "07" => [57.31,58.24...]
                                                    }
                            },
               "COLUMN2" => {
                                    "09/06/2008" => {
                                                      "06" => [54.23,55.28...],
                                                      "07" => [56.79,57.30...]
                                                    }
                            },
               "COLUMN3" => {
                                    "09/06/2008" => {
                                                      "06" => [56.35,54.52...],
                                                      "07" => [56.41,58.86...]
                                                    }
                            }
             );

Взгляните на мой код:

use feature 'switch';
open DATAFILE, "<", $fileName or die "Unable to open $fileName !\n";

    my %monthData;

    while ( my $line = <DATAFILE> ) {

        chomp $line;

        SCANROWS: given ($row) {

            when (0) { # PROCESS HEADERS

                @headers = split /\t\t|\t/, $line;
            }

            default {

                @current = split /\t\t|\t/, $line;
                my $date =  $current[0];
                my ($hour,$min,$sec) = split /:/, $current[1];

                # TIMESTAMP FORMAT: dd/mm/yyyy\t\thh:mm:ss

                SCANLINE: for my $i (2 .. $#headers) {

                    push @{ $monthData{$headers[$i]}{$date}{$hour} }, $current[$i];

                }
            }
        }
    }

    close DATAFILE;

    foreach (@{ $monthData{"Active Power N Avg"}{"09/07/08"}{"06"} }) {
        $sum += $_;
        $count++;
    }

    $avg = $sum/$count; # $sum and $count are not initialized to begin with.
    print $avg; # hence $avg is also not defined.

Надеюсь, моя потребность достаточно ясна. Как я могу добавить значения в массив внутри этих под-хэшей?


person Zaid    schedule 06.07.2009    source источник
comment
Сбросьте свой хэш с помощью Data :: Dumper здесь.   -  person VP.    schedule 07.07.2009
comment
Спасибо, ВП. Data :: Dumper позволяет мне распечатать мои данные (используя print Data :: Dumper- ›Dump ([$ hash {Frequency Min}], [09/07/08]);). Я предполагаю, что теперь могу использовать этот сложный хэш массивов для выполнения некоторых вычислений. Сообщите мне, если это не так.   -  person Zaid    schedule 07.07.2009
comment
@Zaid: Data :: Dumper ничего не исправляет в вашем коде. Он просто позволяет вам увидеть, как на самом деле выглядит ваша структура данных. Часто бывает очень полезно, когда у вас есть что-то вроде этого сложного. Скорее всего, вы пытаетесь получить к нему неправильный доступ (или создаете что-то другое, чем вы думаете). Я думаю, вице-президент сказал, что если вы распечатаете здесь дамп структуры, люди смогут лучше вам помочь. Взгляните на код Часа в ответах: выгрузите весь хеш, а не только его часть.   -  person Telemachus    schedule 07.07.2009
comment
@Telemachus: См. Мой пост ниже.   -  person Zaid    schedule 07.07.2009


Ответы (3)


Это должно сделать это за вас.

#!/usr/bin/perl

use strict;
use warnings;

use List::Util qw/sum/;
sub avg { sum(@_) / @_ }

my $fileName = shift;

open my $fh, "<", $fileName
    or die "Unable to open $fileName: $!\n";

my %monthData;

chomp(my @headers = split /\t+/, <$fh>);

while (<$fh>) {
    chomp;
    my %rec;
    @rec{@headers} = split /\t+/;
    my ($hour) = split /:/, $rec{TIME}, 2;

    for my $key (grep { not /^(DATE|TIME)$/ } keys %rec) {
        push @{ $monthData{$key}{$rec{DATE}}{$hour} }, $rec{$key};
    }
}

for my $column (keys %monthData) {
    for my $date (keys %{ $monthData{$column} }) {
        for my $hour (keys %{ $monthData{$column}{$date} }) {
            my $avg = avg @{ $monthData{$column}{$date}{$hour} };
            print "average of $column for $date $hour is $avg\n";
        }
    }
}

На что следует обратить внимание:

  • strict и предупреждения прагмы
  • Модуль List :: Util для получения функции суммы
  • помещение массива в скалярный контекст, чтобы получить количество элементов в массиве (в функции avg)
  • более безопасная версия open с тремя аргументами
  • лексический дескриптор файла (а не старый дескриптор в стиле голого слова)
  • чтение заголовков сначала вне цикла, чтобы избежать необходимости иметь внутри него особую логику
  • использование хэш-фрагмента для преобразования данных файла в структурированную запись
  • избегая разделения времени больше, чем необходимо, с помощью третьего аргумента для разделения
  • избежать бесполезных переменных, указав только переменную, которую мы хотим поймать, в назначении списка
  • с помощью grep, чтобы ключи DATE и TIME не помещались в% monthData
  • вложенные циклы for, каждый из которых имеет дело с уровнем в хэше
person Chas. Owens    schedule 06.07.2009
comment
Я думаю, что вы, возможно, скопировали опечатку OP: вы хотите ! или $! в вызове die? - person Telemachus; 07.07.2009
comment
@Chas: Мне нравится ваша подпрограмма avg ... синтаксически она сладкая. И большинство ваших предостережений для меня в новинку. Я еще не совсем понимаю функцию grep. - person Zaid; 07.07.2009
comment
Сначала сложно понять функции grep и map, но потом вы понимаете, что это просто забавный способ написания цикла, и все это имеет смысл. Основная идея с grep заключается в том, что блок кода запускается для каждого элемента в списке, если блок кода возвращает true, тогда элемент попадает в возвращаемый список, в противном случае он отбрасывается, поэтому (grep {$ _ ‹5 } 1 .. 10) возвращает (6 .. 10). В программе нам нужны были все ключи, кроме ДАТЫ и ВРЕМЕНИ, поэтому блок кода представляет собой регулярное выражение / ^ (ДАТА | ВРЕМЯ) $ /, которое инвертировано, поэтому он вернет любую строку, отличную от ДАТЫ или ВРЕМЕНИ. - person Chas. Owens; 08.07.2009
comment
@hexcoder Ага, я, наверное, хотел набрать grep { $_ > 5 } 1 .. 10 (что на самом деле вернет 6 .. 10). - person Chas. Owens; 30.06.2011

Я надеюсь, что следующая программа заполнит нужную вам структуру данных:

#!/usr/bin/perl                        

use strict;
use warnings;
use Data::Dumper;

open my $fh, '<', 'input' or die $!;

my @headers;
for ( split /\t/, ~~ <$fh> ) {
    chomp;
    push @headers, $_ unless /^\t?$/;
}

my %monthData;
while (<$fh>) {
    my @line;
    for ( split /\t/ ) {
        chomp;
        push @line, $_ unless /^\t?$/;
    }

    for my $i ( 2 .. $#headers ) {
        my ($hour) = split /:/, $line[1];
        push @{ $monthData{ $headers[$i] }->{ $line[0] }->{$hour} }, $line[$i];
    }
}

print Dumper \%monthData;
person Alan Haggai Alavi    schedule 07.07.2009

Вот как я бы написал для этого программу.

#! /usr/bin/env perl
use strict;
use warnings;
use 5.010; # for say and m'(?<name>)'

use YAML;
use Data::Dump 'dump';

my(%data,%original);
while( my $line = <> ){
  next unless $line =~ m'
    ^ \s*
      (?<day>   0?[1-9] | [12][0-9] | 3[0-1] ) /
      (?<month> 0?[1-9] | 1[0-2] ) /
      (?<year>  [0-9]{4} )
      \s+
      (?<hour>   0?[1-9] | 1[0-9] | 2[0-4] ) :
      (?<minute> 0?[1-9] | [1-5][0-9] ) :
      (?<second> 0?[1-9] | [1-5][0-9] )
      \s+
      (?<columns> .* )
  'x;
  my @columns = split ' ', $+{columns};

  push @{
    $data{ $+{year}  }
         { $+{month} }
         { $+{day}   }
         { $+{hour}  }
  }, \@columns; # or [@columns]

  # If you insist on having it in that data structure you can do this:
  my $count = 1;
  my $date = "$+{day}/$+{month}/$+{year}";
  for my $column ( @columns ){
    my $col = 'COLUMN'.$count++;
    push @{ $original{$col}{$date}{$+{hour}} }, $column;
  }
}

say Dump \%data, \%original; # YAML
say dump \%data, \%original; # Data::Dump

Учитывая этот ввод

DATE       TIME     COLUMN1 COLUMN2 COLUMN3
09/06/2008 06:12:56 56.23   54.23   56.35
09/06/2008 06:42:56 56.73   55.28   54.52
09/06/2008 07:12:56 57.31   56.79   56.41
09/06/2008 07:42:56 58.24   57.30   58.86

Либо "perl program.pl datafile", либо "perl program.pl < datafile"

YAML

---
2008:
  06:
    09:
      06:
        -
          - 56.23
          - 54.23
          - 56.35
        -
          - 56.73
          - 55.28
          - 54.52
      07:
        -
          - 57.31
          - 56.79
          - 56.41
        -
          - 58.24
          - 57.30
          - 58.86
---
COLUMN1:
  09/06/2008:
    06:
      - 56.23
      - 56.73
    07:
      - 57.31
      - 58.24
COLUMN2:
  09/06/2008:
    06:
      - 54.23
      - 55.28
    07:
      - 56.79
      - 57.30
COLUMN3:
  09/06/2008:
    06:
      - 56.35
      - 54.52
    07:
      - 56.41
      - 58.86

Вывод данных

(
  {
    2008 => {
          "06" => {
                "09" => {
                      "06" => [["56.23", "54.23", "56.35"], ["56.73", "55.28", "54.52"]],
                      "07" => [["57.31", "56.79", "56.41"], ["58.24", "57.30", "58.86"]],
                    },
              },
        },
  },
  {
    COLUMN1 => {
                 "09/06/2008" => { "06" => ["56.23", "56.73"], "07" => ["57.31", "58.24"] },
               },
    COLUMN2 => {
                 "09/06/2008" => { "06" => ["54.23", "55.28"], "07" => ["56.79", "57.30"] },
               },
    COLUMN3 => {
                 "09/06/2008" => { "06" => ["56.35", "54.52"], "07" => ["56.41", "58.86"] },
               },
  },
)
person Brad Gilbert    schedule 07.07.2009