Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining 2+ 'deep' (multi-dimensional) hashes in perl

There is a question that explains exactly what I want here: how to merge 2 deep hashes in perl

However, the answer there does not seem to work for me (suggestions of using the Merge module).

I have two hashes like so:

$VAR1 = {
          '57494' => {
                       'name' => 'John Smith',
                       'age' => '9',
                       'height' => '120'
                     },
          '57495' => {
                       'name' => 'Amy Pond',
                       'age' => '17',
                       'height' => '168'
                     }
        }
};
$VAR1 = {
          '57494' => {
                       'name_address' => 'Peter Smith',
                       'address' => '5 Cambridge Road',
                       'post_code' => 'CR5 0FS'
                     }
        }
};

If I use Hash::Merge or the %c = {%a,%b} format I get this every time:

$VAR1 = '57494';
$VAR2 = {
          'name_address' => 'Peter Smith',
          'address' => '5 Cambridge Road',
          'post_code' => 'CR5 0FS'
        };

(so it basically overwrote the first data with the second and messed up the keys) when I want:

$VAR1 = {
          '57494' => {
                       'name' => 'John Smith',
                       'age' => '9',
                       'height' => '120'
                       'name_address' => 'Peter Smith',
                       'address' => '5 Cambridge Road',
                       'post_code' => 'CR5 0FS'
                     },
          '57495' => {
                       'name' => 'Amy Pond',
                       'age' => '17',
                       'height' => '168'
                     }
        }
};

So when the keys are the same, the data merges together, otherwise the new keys are just appended onto the end. I hope this make sense. Maybe I've done something incorrectly using Merge or need to 'manually' add them in loops, but I'm spending too much time thinking about it, regardless!

Edit: how I use Merge to see if I'm doing something silly:

I have:

use Hash::Merge qw( merge );

...hash data above as %hash1 and %hash2...

my %combined_hash = %{ merge( %hash1,%hash2 ) };
print Dumper(%combined_hash);
like image 872
dgBP Avatar asked Feb 19 '23 14:02

dgBP


2 Answers

If I do it with references, it works like a charm.

use strict; use warnings;
use Data::Dumper;
use Hash::Merge qw(merge);
my $h1 = {
  '57494' => {
    'name'   => 'John Smith',
    'age'    => '9',
    'height' => '120'
  },
  '57495' => {
    'name'   => 'Amy Pond',
    'age'    => '17',
    'height' => '168'
  }
};

my $h2 = {
  '57494' => {
    'name_address' => 'Peter Smith',
    'address'      => '5 Cambridge Road',
    'post_code'    => 'CR5 0FS'
  }
};

my $h3 = merge( $h1, $h2 );
print Dumper $h3;

Output:

$VAR1 = {
      '57495' => {
                   'name' => 'Amy Pond',
                   'age' => '17',
                   'height' => '168'
                 },
      '57494' => {
                   'name_address' => 'Peter Smith',
                   'name' => 'John Smith',
                   'post_code' => 'CR5 0FS',
                   'address' => '5 Cambridge Road',
                   'height' => '120',
                   'age' => '9'
                 }
    };

If, however, I do it with hashes instead of hash refs, it doesn't:

my %hash1 = (
  '57494' => {
    'name'   => 'John Smith',
    'age'    => '9',
    'height' => '120'
  },
  '57495' => {
    'name'   => 'Amy Pond',
    'age'    => '17',
    'height' => '168'
  }
);

my %hash2 = (
  '57494' => {
    'name_address' => 'Peter Smith',
    'address'      => '5 Cambridge Road',
    'post_code'    => 'CR5 0FS'
  }
);

my %h3 = merge( %hash1, %hash2 );
print Dumper \%h3;

__END__
$VAR1 = {
  '57495' => undef
};

That is because the merge from Hash::Merge can only take references, but you are passing it hashes. In addition, you need to call it in scalar context.

Try it like so:

#                             +--------+--- references
#   ,-- SCALAR context        |        |
my $combined_hash = %{ merge( \%hash1, \%hash2 ) };
print Dumper($combined_hash);
like image 122
simbabque Avatar answered Feb 28 '23 00:02

simbabque


for my $key (keys %fromhash) {
    if(not exists $tohash{$key}) {
        $tohash{$key} = {};
    }
    for my $subkey (keys %{$fromhash{$key}}) {
        ${$tohash{$key}}{$subkey} = ${$fromhash{$key}}{$subkey};
    }
}

With more or less braces depending on whether my last coffee was any good.

Python is definitely more comfortable for this kind of thing, because it doesn't make you think about references:

for key in fromdict:
    if key not in todict:
        todict[key] = {}
    todict[key] = dict(fromdict[key].items() + todict[key].items())

Or if todict is a defaultdict (creating keys on read as well as assignment):

for key in fromdict:
    todict[key] = dict(dict(fromdict[key]).items() + dict(todict[key]).items())
like image 44
Phil H Avatar answered Feb 27 '23 23:02

Phil H