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

StackOverflow https://stackoverflow.com/questions/12046217

Вопрос

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
Это было полезно?

Решение

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

Другие советы

Your problems:

While buidling your data structures you have made a few mistakes:

  • You always assign a new $key to the same $hash{name} field. Previous contents are overwritten. The last value Guys stays.
  • You push a reference of the %hash into the @array. Because the reference reflects all the changes of the original object, you get the same output every time.

The solution:

Use a different data structure ;-)

Your data works like this: We have a Set of names that each carry a List of values. Sets are modelled with hashes in Perl, so we use one:

my %hash = ();

Each entry will contain a reference to an array containing the values. The final data structure will look like this:

%hash = (
    Sports => ["Basketball", "Tennis", "Boxing"],
    Guys   => ["Tom", "Dick", "Harry"]
);

You can inspect your current data structure with the Data::Dumper module:

use Data::Dumper;
print Dumper (\%name); # has to be given references

To use this data structure, only minimal improvements have to be made to your code:

my %hash;
my $key;
while (my $line = <DATA>) {
    chomp $line;        
    if ($line =~ /^(\S+)/) {
        $key = $1;
        $hash{$key} = []; # this is the main difference
        print "Defined a name $key\n";
     } else {
        $line =~ s/^\s+//;
        push @{ $hash{$key} }, $line;
        print "Defined an arg $line\n";
     }
}

I changed the coding style a little bit as well, but essentially the only difference is that I use the name as a hash key. I remember the last $key used outside the loop so that all args know where to go.

When I want to print out the data structure, I would use the each function if the order of names in not important:

while (my ($name, $arrayRef) = each %hash) {
   my @args = @$arrayRef; # dereference the array
   print qq{name $name with args "@args"\n};
}

or

while (my ($name, $arrayRef) = each %hash) {
   print "name\t$name\n";
   print "arg\t$_\n" foreach @$arrayRef;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top