Массив хэшей

В perl у меня есть массив хэшей, например

0  HASH(0x98335e0)
   'title' => 1177
   'author' => 'ABC'
   'quantity' => '-100'


1  HASH(0x832a9f0)
   'title' => 1177
   'author' => 'ABC'
   'quantity' => '100'

2  HASH(0x98335e0)
   'title' => 1127
   'author' => 'DEF'
   'quantity' => '5100'


3  HASH(0x832a9f0)
   'title' => 1277
   'author' => 'XYZ'
   'quantity' => '1030'

Теперь мне нужно накопить количество, где название и автор совпадают. В приведенной выше структуре для хэша с заголовком = 1177 и автором = «ABC» количество может быть объединено в одно, и вся структура должна выглядеть так, как показано ниже.

0  HASH(0x98335e0)
   'title' => 1177
   'author' => 'ABC'
   'quantity' => 0

1  HASH(0x98335e0)
   'title' => 1127
   'author' => 'DEF'
   'quantity' => '5100'

2  HASH(0x832a9f0)
   'title' => 1277
   'author' => 'XYZ'
   'quantity' => '1030'

Каков наилучший способ сделать это накопление, чтобы оно было оптимизировано? Количество элементов массива может быть очень большим. Я не возражаю против добавления дополнительного ключа к хэшу, чтобы помочь тому же самому, но я не хочу n поисковых запросов. Добрый совет


person Gopalakrishnan SA    schedule 08.07.2010    source источник
comment
Вы говорите, что мне не нужны n поисковых запросов, но нет способа накапливать по всему массиву, не обращаясь к каждому члену массива.   -  person JSBձոգչ    schedule 08.07.2010
comment
Пожалуйста, добавьте perldoc perldsc и perldoc perlreftut в свой список для чтения.   -  person Ether    schedule 08.07.2010


Ответы (3)


my %sum;
for (@a) {
  $sum{ $_->{author} }{ $_->{title} } += $_->{quantity};
}

my @accumulated;
foreach my $author (keys %sum) {
  foreach my $title (keys %{ $sum{$author} }) {
    push @accumulated => { title    => $title,
                           author   => $author,
                           quantity => $sum{$author}{$title},
                         };
  }
}

Не уверен, что map делает его лучше:

my @accumulated =
  map {
    my $author = $_;
    map { author   => $author,
          title    => $_,
          quantity => $sum{$author}{$_},
        },
      keys %{ $sum{$author} };
  }
  keys %sum;
person Greg Bacon    schedule 08.07.2010
comment
Этот образец просто жаждет немного любви к map/grep - person Daenyth; 08.07.2010
comment
@Daenyth Обычно да, но в данном случае это выглядит не так красиво. - person Greg Bacon; 08.07.2010

Если вам не нужны N поисковых запросов, вам нужна хеш-функция, однако вам нужно сохранить их с помощью этой хеш-функции. К тому времени, когда они появятся в списке (или массиве), будет слишком поздно. Либо вам повезет постоянно, либо у вас будет N поисковых запросов.

Или вставьте их в хэш вышениже. Гибридное решение состоит в том, чтобы сохранить локатор как элемент 0 в списке/массиве.

my $lot = get_lot_from_whatever();
my $tot = $list[0]{ $lot->{author} }{ $lot->{title} };
if ( $tot ) { 
    $tot->{quantity} += $lot->{quantity};
}
else { 
    push @list, $list[0]{ $lot->{author} }{ $lot->{title} } = $lot;
}        

предыдущий

Прежде всего, мы переформатируем это, чтобы сделать его читабельным.

[ { title => 1177, author => 'ABC', quantity => '-100' }
, { title => 1177, author => 'ABC', quantity => '100'  }
, { title => 1127, author => 'DEF', quantity => '5100' }
, { title => 1277, author => 'XYZ', quantity => '1030' }
]

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

Поскольку у нас есть две вещи, двойной хэш — хороший способ сделать это.

my %hash;
foreach my $lot ( @list ) {
    $hash{ $lot->{author} }{ $lot->{title} } += $lot->{quantity};
}
# consolidated by hash

Чтобы превратить это обратно в список, нам нужно разделить уровни.

my @consol
    = sort { $a->{author} cmp $b->{author} || $a->{title} cmp $b->{title} }
      map  { 
          my ( $a, $titles ) = @$_; # $_ is [ $a, {...} ]
          map { +{ title => $_, author => $a, quantity => $titles->{$_} }
          keys %$titles;
      } 
      map  { [ $_ => $hash{$_} ] } # group and freeze a pair
      keys %hash
    ;

# consolidated in a list.

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

sort {  $b->{quantity} <=> $a->{quantity} 
     || $a->{author}   cmp $b->{author} 
     || $a->{title}    cmp $b->{title} 
     }
person Axeman    schedule 08.07.2010

Я думаю, что важно сделать шаг назад и рассмотреть источник данных. Если данные поступают из базы данных, вам следует написать SQL-запрос так, чтобы он давал вам одну строку для каждой комбинации автора/названия с общим количеством в поле количества. Если вы читаете данные из файла, вам следует либо прочитать их непосредственно в хэш, либо использовать Tie::IxHash, если важен порядок.

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

person Sinan Ünür    schedule 08.07.2010