Vishwasu Deshpande
Vishwasu Deshpande

Reputation: 59

How do I merge two hashes that start with different keys in Perl?

I have to hashes as follows:

my %hash1 = (
    'modules' => {
        'top0' => {
            'instances' => {
                'sub_top' => {
                    'instances' => {
                        'inst2' => 2,
                        'inst0' => 0
                    }
                }
            }
        }
    }
);

my %hash2 = (
    'modules' => {
        'sub_top' => {
            'instances' => {
                'inst0' => 0,
                'inst1' => 1
            }
        }
    }
);                                                                   }

I need to merge these into one hash. I tried using Hash::Merge but that works only if I start from subtop onwards only.

my $merged_hash = merge(\%{$hash1{modules}{top0}{instances}}, \%{$hash2{modules}});

Dumping the resulting $merged_hash gives:

$VAR1 = {
    'sub_top' => {
        'instances' => {
            'inst2' => 2,
            'inst0' => 0,
            'inst1' => 1
        }
    }
};

But I miss the top part of hash1:

'modules' => {
    'top0' => {
        'instances' => {

Desired hash after merging should be like this:

$VAR1 = {
    'modules' => {
        'top0' => {
            'instances' => {
                'sub_top' => {
                    'instances' => {
                        'inst2' => 2,
                        'inst0' => 0,
                        'inst1' => 1
                    }
                }
            }
        }
    }
};

Upvotes: 0

Views: 134

Answers (2)

Polar Bear
Polar Bear

Reputation: 6798

One of possible ways to merge hashes on predefined key. See if it satisfies your expectation.

use strict;
use warnings;
use feature 'say';

use Data::Dumper;

my $debug = 0;

my %hash1 = (
    'modules' => {
        'top0' => {
            'instances' => {
                'sub_top' => {
                    'instances' => {
                        'inst2' => 2,
                        'inst0' => 0
                    }
                }
            }
        }
    }
);

my %hash2 = (
    'modules' => {
        'sub_top' => {
            'instances' => {
                'inst0' => 0,
                'inst1' => 1
            }
        }
    }
); 

my $key = 'sub_top';

mergeOnKey(\%hash1, \%hash2, $key);

say Dumper(\%hash1);

sub mergeOnKey {
    my $h1 = shift;
    my $h2 = shift;
    my $k  = shift;

    my $href1 = find_ref($h1,$k);
    my $href2 = find_ref($h2,$k);

    die 'Hash 1 no key was found' unless $href1;
    die 'Hash 2 no key was found' unless $href2;

    merge($href1,$href2);
}

sub merge {
    my $href1 = shift;
    my $href2 = shift;

    foreach my $key (keys %{$href2}) {
        if( ref $href2->{$key} eq ref {} ) {
            merge($href1->{$key},$href2->{$key});
        } else {
            $href1->{$key} = $href2->{$key} unless defined $href1->{$key};
        }
    }

    say Dumper($href1) if $debug;
}

sub find_ref {
    my $href = shift;
    my $key  = shift;

    while( my($k,$v) = each %{$href} ){
        say Dumper($v) if $debug;
        return $v if $k eq $key;
        return find_ref($v,$key) if ref $v eq 'HASH';
    }

    return 0;
}

Output

$VAR1 = {
    'modules' => {
        'top0' => {
            'instances' => {
                'sub_top' => {
                    'instances' => {
                        'inst1' => 1,
                        'inst2' => 2,
                        'inst0' => 0
                    }
                }
            }
        }
    }
};

Upvotes: 3

Christopher Bottoms
Christopher Bottoms

Reputation: 11158

If you look at the Synopsis for Hash::Merge, the example shows two hashes that look practically identical in structure. The hashes that you want to merge may have many similarities, but they also have significant differences. So, I would only expect that you can only merge them where their structure is sufficiently "equivalent". Here is the most obvious way that I can think of to get them to merge to the resulting hash you indicate:

#!/bin/env perl
use strict;
use warnings;
use Test::More;    
use Hash::Merge qw(merge);

my %hash1 = (
    'modules' => {
        'top0' => {
            'instances' => {
                'sub_top' => {
                    'instances' => {
                        'inst2' => 2,
                        'inst0' => 0
                    }
                }
            }
        }
    }
);

my %hash2 = (
    'modules' => {
        'sub_top' => {
            'instances' => {
                'inst0' => 0,
                'inst1' => 1
            }
        }
    }
);

# Merge the desired parts of the structure
$hash1{modules}{top0}{instances} =
  merge( \%{ $hash1{modules}{top0}{instances} }, \%{ $hash2{modules} } );

# Demonstrate that merge worked as desired
my %desired = (
    'modules' => {
        'top0' => {
            'instances' => {
                'sub_top' => {
                    'instances' => {
                        'inst2' => 2,
                        'inst0' => 0,
                        'inst1' => 1
                    }
                }
            }
        }
    }
);

is_deeply( \%hash1, \%desired, 'Desired hash is created' );

done_testing();

Upvotes: 1

Related Questions