2014-10-24 19 views
5

Próbuję połączyć dwa hashe, które zawierają jedną lub więcej tablic przy użyciu Hash::Merge. Na przykład:Scal hashe z tablicami z Hash :: Merge

use strict; 
use warnings; 
use feature qw(say); 

use Data::Dump qw(dump); 
use Hash::Merge qw(merge); 

my $h1 = { a => [ { aa => 1 }, 3 ] }; 
my $h2 = { a => [ { bb => 2 } ] }; 

my $hLeft = merge($h1, $h2); 
my $hRight = merge($h2, $h1); 

say " hLeft: " . dump($hLeft); 
say " hRight: " . dump($hRight); 

my $hDesired = { a => [ { aa => 1, bb => 2 }, 3 ] }; 
say "Desired: " . dump($hDesired); 

Daje wyjście:

hLeft: { a => [{ aa => 1 }, 3, { bb => 2 }] } 
hRight: { a => [{ bb => 2 }, { aa => 1 }, 3] } 
Desired: { a => [{ aa => 1, bb => 2 }, 3] } 

Jak mogę uzyskać poprawny wynik używając Hash::Merge?

Odpowiedz

1

Zachowanie domyślne dla łączących tablic jest dołączyć je:

sub { [ @{$_[0]}, @{$_[1]} ] }, 

aby uzyskać różne zachowanie, należy użyć Hash::Merge::specify_behavior.

Poniższy rozwiązaniem jest LEFT_PRECEDENT i łączy elementy tablic do elementu:

use strict; 
use warnings; 
use feature qw(say); 

use Data::Dump qw(dump); 
use Hash::Merge qw(merge); 

Hash::Merge::specify_behavior(
    { 'SCALAR' => { 
      'SCALAR' => sub { $_[0] }, 
      'ARRAY' => sub { $_[0] }, 
      'HASH' => sub { $_[0] }, 
     }, 
     'ARRAY' => { 
      'SCALAR' => sub { [ @{ $_[0] }, $_[1] ] }, 
      'ARRAY' => sub { 
       my ($left, $right) = @_; 

       my @merged = @$left; 
       my @to_add = @$right; 

       for (@merged) { 
        last if [email protected]_add; 
        $_ = Hash::Merge::merge($_, shift @to_add); 
       } 

       return [ @merged, @to_add ]; 
      }, 
      'HASH' => sub { [ @{ $_[0] }, values %{ $_[1] } ] }, 
     }, 
     'HASH' => { 
      'SCALAR' => sub { $_[0] }, 
      'ARRAY' => sub { $_[0] }, 
      'HASH' => sub { Hash::Merge::_merge_hashes($_[0], $_[1]) }, 
     }, 
    }, 
    'My Behavior', 
); 

my $h1 = { a => [ { aa => 1 }, 3 ] }; 
my $h2 = { a => [ { bb => 2 } ] }; 

my $merged = merge($h1, $h2); 
say "Merged: " . dump($merged); 

Wyjścia:

Merged: { a => [{ aa => 1, bb => 2 }, 3] } 
+0

Dzięki Miller! Z twojego kodu widzę, że powinienem użyć 'scalania' zamiast' _merge_hashes'. Lubię twoją uproszczoną wersję znacznie bardziej niż moją :) –

+1

Prawidłowo, Argumenty dla _merge_hash muszą być hashami. Sprawdzałeś, czy są to referencje, ale błąd zostałby rzucony, gdyby tablica utknęła w twoich tablicach. Ponadto użycie funkcji scalania uprościło logikę, ponieważ umożliwiło modułowi obsługę typów danych. NP za pomoc i dzięki za inspirację do znalezienia nowego narzędzia. – Miller

3

Można to zrobić za pomocą Hash::Merge::specify_behavior:

use warnings; 
use strict; 
use Data::Dump 'dump'; 
use Hash::Merge; 
use feature 'say'; 

Hash::Merge::specify_behavior 
    ({ 
    'SCALAR' => { 
       'SCALAR' => sub { $_[1] }, 
       'ARRAY' => sub { [ $_[0], @{$_[1]} ] }, 
       'HASH' => sub { $_[1] }, 
       }, 
    'ARRAY' => { 
       'SCALAR' => sub { $_[1] }, 
       'ARRAY' => \&mergeArrays, 
       'HASH' => sub { $_[1] }, 
       }, 
    'HASH' => { 
       'SCALAR' => sub { $_[1] }, 
       'ARRAY' => sub { [ values %{$_[0]}, @{$_[1]} ] }, 
       'HASH' => sub { Hash::Merge::_merge_hashes($_[0], $_[1]) }, 
       }, 
    }, 
    'My Behavior', 
); 

my $h1={a=>[{aa=>1},3]}; 
my $h2={a=>[{bb=>2}]}; 

my $hMerge=Hash::Merge::merge($h1,$h2); 
say "hMerge: ".dump($hMerge); 

sub mergeArrays{ 
    my ($a,$b)[email protected]_; 

    my ($na,$nb)=($#$a,$#$b); 
    my @c; 
    if ($na>$nb) { 
     @[email protected]$a[($nb+1)..$na]; 
     return mergeArrays2($a,$b,\@c,$nb); 
    } else { 
     @[email protected]$b[($na+1)..$nb]; 
     return mergeArrays2($a,$b,\@c,$na); 
    } 
} 

sub mergeArrays2{ 
    my ($a,$b,$c,$n)[email protected]_; 

    my $r=[]; 
    for my $i (0..$n) { 
     if (ref($a->[$i]) && ref($b->[$i])) { 
      push(@$r,Hash::Merge::_merge_hashes($a->[$i],$b->[$i])); 
     } else { 
      push(@$r,$a->[$i]); 
     } 
    } 
    push(@$r,@$c); 
    return $r; 
} 

wyjściowa:

hMerge: { a => [{ aa => 1, bb => 2 }, 3] } 
+1

Wydaje się, że należy skopiować i wkleić swoje rozwiązanie z docs dla 'Hash :: Merge '. Należy pamiętać, że podany przykład to RIGHT_PRECEDENT. Jednak wyspecjalizowana kombinacja tablicowa, którą kodujesz, to LEFT_PRECEDENT. Dlatego zakodowałem własne rozwiązanie LEFT_PRECEDENT, a także używam znacznie prostszej logiki do scalania. – Miller