Помощь с ходьбой / сортировкой сложной структуры данных Perl (HoH с AoH fun) - PullRequest
3 голосов
/ 03 мая 2011

Я уже пару часов бьюсь головой об стену.

У меня есть структура данных, которая выглядит примерно так (вывод «Data :: Dumper»).Это моя вина, я создаю структуру данных, когда анализирую некоторые данные.

print Dumper $data;

___OUTPUT___
$VAR = { 'NAME' => {
                    'id' => '1234',
                    'total' => 192,
                    'results' =>  { 
                                     'APPLE'   => 48 ,
                                     'KUMQUAT' => 61 ,
                                     'ORANGE'  => 33 ,
                                  }
                   }

       }
  • Существуют тысячи ключей "NAME".
  • Есть толькокогда-либо один "id" и один "total".
  • В хэше "results" может быть одна или несколько пар ключ / значение.

Я хочу распечатать запятуюразделенный список, сначала отсортированный по «итогу», а затем по значению каждого хеша в массиве «результатов».

Следующий код был использован для распечатки CSV из уже сохраненной структуры данных.

use strict;
use warnings;
# [...lots of other stuff...]

open (my $fh, >out.csv);
print $fh "Name, ID, Label, Count, Total\n";

foreach ( sort { $data->{$b}->{total} <=> $data->{$a}->{total} }
    keys %{$data} )
{
    my $name = $_;
    foreach (
        sort {
            $data->{$name}->{results}->{$a} <=> $data->{$name}->{results}
              ->{$b}
        } values %{ $data->{$name}->{results} }
      )
    {

        print $fh $name . ","
          . $data->{$name}->{id} . "," . "'"
          . $_ . ","
          . $data->{$name}->{results}->{$_} . "," . "\n";
    }
    print $fh $name . ","
      . $data->{$name}->{id} . "," . "," . ","
      . $data->{$name}->{total} . "\n";
}

close($fh);

Это было нормально и хорошо работало (кроме напоминания, почему я больше никогда не использую Perl).

Пример вывода был такой:

Name, ID,  Label,   Count, Total
foo, 1234, ORANGE,    33,
foo, 1234, APPLE,     48,
foo, 1234, KUMQUAT,   61,
foo, 1234,     ,        ,  142
bar, 1101, BIKE,      20,
bar  1101,     ,        ,  20

ОДНАКО!Я заметил, что я получаю ключевые коллизии (в хэше «результатов») и, поскольку мне нужно хранить и отчитываться по всем данным, я решил попробовать изменить «результаты» на массив хэшей ...

print Dumper $data;

___OUTPUT___
$VAR = { 'NAME' => {
                    'id' => '1234',
                    'total' => 192,
                    'results' => [
                                   { 'APPLE'   => 48 },
                                   { 'KUMQUAT' => 61 },
                                   { 'ORANGE'  => 33 },
                                   { 'APPLE'   => 50 },
                                 ]
                   }
       }
  • Существуют тысячи ключей "NAME".
  • Всегда существует только один "id" и одна "total".
  • Может быть один или несколько хэшеймассив «results».
  • Каждый хэш в массиве «results» будет иметь только одну пару имя / значение.

Независимо от того, читал ли кто-нибудь это далеко, яДолжен сказать, что это довольно полезно записывать, поэтому я продолжу ...; -)

Для новой структуры данных у меня возникла проблема с кодом сортировки / печати. ​​

use strict;
use warnings;
# [...lots of other stuff...]

open (my $fh, >out.csv);
print $fh "Name, ID, Label, Count, Total\n";

foreach ( sort { $data->{$b}->{total} <=> $data->{$a}->{total} }
    keys %{$data} )
{
    my $name = $_;
    foreach (
        sort {
            $data->{$name}->{results}->{$a} <=> $data->{$name}->{results}
              ->{$b}
        } values %{ $data->{$name}->{results} }
      )
    {
    # .... HELP ME FOR THE LOVE OF ALL THAT IS GOOD IN THE WORLD! ....
    # I'm at the point now where my brain is starting to slowly dribble from my
    # ears...
    }
    print $fh $name . "," 
      . $data->{$name}->{id} . "," . "," . ","
      . $data->{$name}->{total} . "\n";
}

close($fh);

Если вы прочли это далеко, я приветствую вас.Если вы можете помочь, я приветствую вас.

Если у кого-то есть предложения по поводу альтернативного формата структуры данных, пожалуйста, дайте мне знать!(Если вам интересно ... Я использую оператор "flip-flop" для захвата блоков исходного файла, который я затем использую, построчно, для создания структуры данных. Я также называю externalпрограммы для вычисления определенных вещей (без эквивалентов Perl) и сохранения результатов.)

Спасибо

Ответы [ 3 ]

5 голосов
/ 04 мая 2011

Хорошо, я говорю это только один раз: Всегда используйте объекты, когда у вас сложные структуры

Как вы обнаружили, ваш мозг взорвется, пытаясь отследить массивы хэшей массивов хэшей. Это идеальная причина для создания структуры объекта. Неважно, если вы никогда не будете использовать его снова. Это значительно облегчает задачу программирования.

Следующий пакет занял у меня около 30 минут, чтобы написать и отладить. Если бы вы использовали его, вы бы избавили себя от душевной боли и отладки.

В качестве бонуса, когда вы обнаружили свое ошибочное предположение (эй, все так делают!), Что у вас есть несколько элементов с одинаковым ключом в массиве RESULT, вам нужно будет всего лишь изменить несколько строк, чтобы легко найти код вместо прохождения всей вашей программы, пытаясь сохранить все вместе.

Я использовал вашу структуру данных, за исключением того, что я сделал РЕЗУЛЬТАТЫ массивом, содержащим массив с двумя элементами (меткой и количеством) вместо хеша. Я мог бы использовать хеш, но таким образом я могу вернуть массив с двумя элементами в нем. Теперь, когда я об этом думаю, в действительности не было никакой причины делать это.

#! /usr/bin/env perl

use warnings;
use strict;
use feature qw(say);
use Data::Dumper;


my %hash;
my $obj;

$obj = structure->new();
$obj->Name("foo");
$obj->Total("foo", 142);
$obj->Id("foo", 1234);
$obj->Push(qw(foo  ORANGE  33));
$obj->Push(qw(foo  APPLE   48));
$obj->Push(qw(foo  APPLE   50));
$obj->Push(qw(foo  KUMQUAT 61));
$obj->SortResults("foo");

$obj->Name("bar");
$obj->Total("bar", 20);
$obj->Id("bar", 1100);
$obj->Push(qw(bar BIKE    20));
$obj->SortResults("bar");

say Dumper($obj);
exit 0;

########################################################################
package structure;

use Data::Dumper;

#
# New Structure containing all data
# 
sub new {
    my $class = shift;

    my $self = {};

    bless $self, $class;
    return $self;
}

#
# Either adds a new name object or returns name object;
#
sub Name {
    my $self = shift;
    my $name = shift;

    if (not defined $self->{$name}) {
        $self->{$name}->{ID} = undef;
        $self->{$name}->{TOTAL} = undef;
        $self->{$name}->{RESULTS} = [];
    }
    return $self->{$name};
}

#
# Returns a list of Names
#
sub NameList {
    my $self = shift;

    return keys %{$self};
}
#
# Either returns the id or sets $name's id
#
sub Id {
    my $self = shift;
    my $name = shift;
    my $id = shift;

    my $nameObj = $self->Name($name);
    if (defined $id) {
        $nameObj->{ID} = $id;
    }
    return $nameObj->{ID};
}

#
# Either returns the total for $name or sets $name's total
#
sub Total {
    my $self = shift;
    my $name = shift;
    my $total = shift;

    my $nameObj = $self->Name($name);
    if (defined $total) {
        $nameObj->{TOTAL} = $total;
    }
    return $nameObj->{TOTAL};
}

#
# Pushes new product and amount on $name's result list
#
sub Push {
    my $self = shift;
    my $name = shift;
    my $product = shift;
    my $amount = shift;

    my $nameObj = $self->Name($name);
    my @array = ("$name", "$amount");
    push @{$nameObj->{RESULTS}}, \@array;
    return @array;
}

#
# Pops product and amount on $name's result list
#
sub Pop {
    my $self = shift;
    my $name = shift;

    my $nameObj = $self->Name($name);
    my $arrayRef = pop @{$nameObj->{RESULTS}};
    return @{$arrayRef};
}

sub SortResults {
    my $self = shift;
    my $name = shift;

    my $nameObj = $self->Name($name);
    my @results = @{$nameObj->{RESULTS}};
my @sortedResults = sort {$a->[1] <=> $b->[1]} @results;
my $nameObj->{RESULTS} = \@sortedResults;
    return @sortedResults;
}

$obj->SortResults отсортирует результаты на месте, но вы все равно можете использовать их для получения результатов в виде отсортированного списка. Для сортировки предметов по сумме вы могли бы использовать:

my @sortedItems = sort {$obj->Total($a) <=> $obj->Total($b)} $obj->NameList();

Короче, вы бы сэкономили себе время и уборщикам навести порядок. (Разорванный мозг очень трудно вычистить со стен и потолка).

Из опыта я узнал, что всякий раз, когда вы начинаете говорить о хэшах хэшей, которые содержат массивы, указывающие на другие хэши, пришло время создать объект для обработки беспорядка. Может показаться, что создание объектов для такого типа одноразовых заданий занимает гораздо больше времени, но, по моему опыту, вы обычно можете набрать то, что вам нужно, и протестировать за 30 минут, что впоследствии сэкономит вам часы разочарования.

3 голосов
/ 03 мая 2011
use strict;
use warnings;
# [...lots of other stuff...]

open (my $fh, '>', 'out.csv');
print $fh "Name, ID, Label, Count, Total\n";

my $data = {
    'NAME' => {
        'id' => '1234',
        'total' => 192,
        'results' => [
            { 'APPLE'   => 48 },
            { 'KUMQUAT' => 61 },
            { 'ORANGE'  => 33 },
            { 'APPLE'   => 50 },
        ]
    }
};

# sort names by total, descending
for my $name ( sort { $data->{$b}{total} <=> $data->{$a}{total} } keys %{$data} )
{
    # sort results by count, ascending; is this what you want?
    for my $result ( sort { (%$a)[1] <=> (%$b)[1] } @{ $data->{$name}{results} } ) {
        my ($label, $count) = %$result;
        print $fh join(',', $name, $data->{$name}{id}, $label, $count, ''), "\n";
    }
    print $fh join(',', $name, $data->{$name}{id}, '', '', $data->{$name}{total}), "\n";
}

close($fh);
2 голосов
/ 03 мая 2011

Вы, вероятно, могли бы уменьшить уровень сложности с такой структурой данных:

$VAR = [
         {
           'name' => 'foo',
           'id' => '1234',
           'total' => 192,
           'results' => [
                          { 'label' => 'APPLE', 'score' => 48 },
                          { 'label' => 'KUMQUAT', 'score' => 61 },
                          { 'label' => 'ORANGE', 'score' => 33 },
                          { 'label' => 'APPLE', 'score' => 50 },
                        ]
         },
       ];

Так что, если я вспомню мой Perl, вы бы посмотрели что-то вроде:

foreach my $row ( sort( $a->{'total'} <=> $b->{'total'} ) @data ) {

    foreach my $result ( sort( $a->{'score'} <=> $b->{'score'} ) @{$row->{'results'}} ) {

    }

}
...