Search code examples
arraysperlhashhash-of-hashes

How do I iterate through an array of hashes of arrays once per hash, and not once per nested array element?


Apologies if this has been resolved elsewhere. I searched and searched.

I'm working with a config file that separates chunks of data by left-aligning the name of each chunk and indenting arguments for each name (see __DATA__ below). I've written a script that stores each chunk into a hash having a $hash{name} key pointed at a scalar value, and a %hash{args} key pointed at an array of values. As each chunk is stored into its own hash, a reference to the hash is stored anonymously in an array. Eventually I'd like to grab these hashes one by one and process the values in them but I'm having trouble iterating through the array.

When I try to print the values stored in each hash it seems to be taking the hash refs in that array in list context, so if %hash{args} was a reference to an array having three elements, that's three more times the foreach loop runs.

How do I get the code in that loop to just run once for each hash reference I've socked away into that array?

If you check my output it's clear that the nested array reference needs to be dereferenced but I'm so stuck on getting the loop right that I haven't been able to tackle that yet. Maybe the solution will fix both?

Behold:

use strict;
use warnings;

my @array;
my %hash;
my ($name, $args);

while (my $line = <DATA>) {
    chomp($line);        
    if ($line !~ /^\s/) 
    {
        my ($key) = $line =~ /^\S+/g;
        $hash{name} = $key;
        print "Defined a name $key\n";
     } 
     else 
     {
        $line =~ s/^\s+//;
        push (@{ $hash{args} }, $line);
        print "Defined an arg $line\n";
     }
     push (@array, \%hash);
}

foreach my $i (@array)
{
    foreach my $h (keys %{$i}) 
    {
        print $h . "\t";
        print $i->{$h} . "\n";      

    }
}


__DATA__
Sports
    Basketball
    Tennis
    Boxing
Guys
    Tom
    Dick
    Harry

And here's the output:

Defined a name Sports
Defined an arg Basketball
Defined an arg Tennis
Defined an arg Boxing
Defined a name Guys
Defined an arg Tom
Defined an arg Dick
Defined an arg Harry
args    ARRAY(0x4a8e24)
name    Guys
args    ARRAY(0x4a8e24)
name    Guys
args    ARRAY(0x4a8e24)
name    Guys
args    ARRAY(0x4a8e24)
name    Guys
args    ARRAY(0x4a8e24)
name    Guys
args    ARRAY(0x4a8e24)
name    Guys
args    ARRAY(0x4a8e24)
name    Guys
args    ARRAY(0x4a8e24)
name    Guys

Solution

  • I think you need a different data structure. Try this:

    use strict;
    use warnings;
    use Data::Dumper;
    
    my @array;
    my %hash;
    my ( $name, $args, $key );
    
    while ( my $line = <DATA> ) {
        chomp($line);
        if ( $line !~ /^\s/ ) {
            $key = $line;  #=~ /^\S+/g;
            print "Defined a name $key\n";
        }
        else {
            $line =~ s/^\s+//;
            push (@{$hash{$key}}, $line);
            print "Defined an arg $line\n";
        }
    }
    
    print Dumper(\%hash);
    
    __DATA__
    Sports
            Basketball
            Tennis
            Boxing
    Guys
            Tom
            Dick
            Harry
    

    Output:

    $VAR1 = {
              'Sports' => [
                            'Basketball',
                            'Tennis',
                            'Boxing'
                          ],
              'Guys' => [
                          'Tom',
                          'Dick',
                          'Harry'
                        ]
            };
    

    I tried to keep the solution close to your own code.

    Edit: If you deal with multi-level data structures always used Data::Dumper to dump what exactly is in your array/hash. This will help you resolve assignment problems such as this instance.

    Your data structure looked like this:

    $VAR1 = [
              {
                'args' => [
                            'Basketball',
                            'Tennis',
                            'Boxing',
                            'Tom',
                            'Dick',
                            'Harry'
                          ],
                'name' => 'Guys'
              },
              $VAR1->[0],
              $VAR1->[0],
              $VAR1->[0],
              $VAR1->[0],
              $VAR1->[0],
              $VAR1->[0],
              $VAR1->[0]
            ];