Search code examples
perlperl-data-structures

Split hash in hash of array


I have three hashes that I would like to explode each one into a Hash of Arrays. Each one could be passed into a subroutine with a split function, but this requires the same subroutine to be called 3 times. I have tried to iterate through each hash and them split it but without success. Is it possible to do this without calling a subroutine? The code I have tried is:

#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;

my %hash1 = (
    A => "AAAAAAAAAA",
    B => "AAAAANAAAA",
    C => "AAAAAXAAAA",
    D => "AAXAAAAAAA",
    E => "AAAAAAAAAX",
);

my %hash2 = (
    F => "BBBBBBBBBB",
    G => "BBBBBBBBBB",
    H => "BBXBBBBBBB",
    I => "BBBBBBBBBB",
    J => "AAAAANAAAA",
);

foreach my $ref ( \%hash1, \%hash2 ) {
    while ( my ($key, $value) = each %$ref) {
        @{ $ref->{$key} } = split (//, $value );
    }
}

print Dumper %hash1;

but gives the warning:

Can't use string ("AAAAAAAAAA") as an ARRAY ref while "strict refs" in use

Thanks in advance.


Solution

  • Your mistake is that you are taking the value of the key $ref->{$key} which is AAAAAAA etc, and using it as a reference with the @{ ... } braces. You need to assign to $ref->{$key} directly.

    Since you are splitting the original value into a list, you need an array to store it. You can do this either by using a named array, lexically scoped, or an anonymous array. My choice would be to use an anonymous array, but a named array might appear more readable:

    foreach my $ref ( \%hash1, \%hash2 ) {
        while ( my ($key, $value) = each %$ref) {
            $ref->{$key} = [ split (//, $value ) ];  # using anonymous array
            # my @list = split (//, $value );
            # $ref->{$key} = \@list;                 # using named array
        }
    }
    

    This process would overwrite the original values, but I assume that is what you want.

    A more direct way to achieve the same thing would be this:

    $_ = [ split // ] for values %hash1, values %hash2;
    

    Here we are (ab)using the fact that the elements in a for loop are aliased to the original variables, and simply overwriting the values the same way we did above.

    If the compressed format is unwanted, a more verbose alternative would be:

    for my $value (values %hash1, values %hash2) {
        $value = [ split //, $value ];
    }