2014-10-24 17 views
5

Sto cercando di unire due hash che contengono uno o più array utilizzando Hash::Merge. Per esempio:Unisci gli hash con gli array con 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); 

Questo dà uscita:

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

Come posso ottenere l'output corretto utilizzando Hash::Merge?

risposta

1

Il comportamento predefinito per le matrici fusione è di aggiungere loro:

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

Per ottenere un comportamento diverso, uno deve usare Hash::Merge::specify_behavior.

La seguente soluzione è LEFT_PRECEDENT, e si fonde elemento array per elemento:

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); 

Uscite:

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

Grazie Miller! Vedo dal tuo codice che avrei dovuto usare 'unire' invece di' _merge_hashes'. Mi piace la tua versione semplificata molto più della mia :) –

+1

Corretto, gli argomenti per _merge_hashes devono essere riferimenti hash. Stavi controllando che fossero ref, ma sarebbe stato gettato un errore se un arrayref fosse entrato nei tuoi array. Inoltre, utilizzando l'unione è stata semplificata la logica poiché ha consentito al modulo di gestire la gestione del tipo di dati. NP per aiuto, e grazie per l'ispirazione per il ritiro di un nuovo strumento. – Miller

3

questo può essere fatto utilizzando 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; 
} 

uscita:

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

Sembra che si copia e incollato la soluzione dalla documentazione per 'Hash :: Merge '. È importante notare che l'esempio che hanno fornito era RIGHT_PRECEDENT. Tuttavia, l'array specializzato si fonde con il codice LEFT_PRECEDENT. Ho quindi codificato la mia soluzione che è LEFT_PRECEDENT e utilizza anche una logica molto più semplice per l'unione. – Miller