Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Push array to hash

by bartrad (Beadle)
on Jun 24, 2020 at 14:32 UTC ( #11118431=perlquestion: print w/replies, xml ) Need Help??

bartrad has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I've got a strange problem I can't get my head round. I'm pushing a hash and the first one appears in OK, but subsequent ones appear wrong?

$VAR1 = \[ { 'desc' => 'xxx', 'ports' => [ { 'adminstate' => 'up', 'operationstate' => 'down', 'port' => '80', 'ismaster' => 'active' }, { 'adminstate' => 'up', 'operationstate' => 'down', 'port' => '81', 'ismaster' => 'active' }, { 'adminstate' => 'up', 'operationstate' => 'down', 'port' => '82', 'ismaster' => 'active' } ], 'adminstate' => 'up', 'lagid' => '45', 'operationstate' => 'down' }, ${$VAR1}->[0], ${$VAR1}->[0], ${$VAR1}->[0], ${$VAR1}->[0],

The first structure appears just fine, but subsequent pushes only show as "${$VAR1}->[0]" - not sure why?

foreach my $line ( split( /\n/, $c ) ) { if ( $line =~ m/someregex/ ) { if ( exists( $lag_detail{"ports"} ) ) { push( @{ $self->{"lags"} }, \%lag_detail ); } %lag_detail = ( "lagid" => $lag, "lagdesc" => $4, "operationst +ate" => $3, "adminstate" => $2 ); } } if ( $line =~ m/somemoreregex/ ) { my %port_detail = ( "port" => $1, "adminstate" => $2, "operationst +ate" => $4, "ismaster" => $3 ); push @{ $lag_detail{"ports"} }, \%port_detail; } }

Replies are listed 'Best First'.
Re: Push array to hash
by BillKSmith (Prior) on Jun 24, 2020 at 16:07 UTC
    Your dump is telling you that you that you have pushed multiple reference to the same variable. There is no point in printing its value multiple times. I cannot offer a real fix without seeing the declaration of your variables and the statement that prints the dump. Perhaps you need:
    push( @{ $self->{"lags"} }, {%lag_detail} );

    This would push a reference to an anonymous hash which contains a current copy of your hash.

    Bill
      ... a current copy of your hash.

      A shallow copy of the hash. This may be sufficient for bartrad's use case, but I think it's always worth emphasizing the difference between shallow and deep copying.


      Give a man a fish:  <%-{-{-{-<

Re: Push array to hash
by haukex (Bishop) on Jun 25, 2020 at 07:03 UTC
    The first structure appears just fine, but subsequent pushes only show as "${$VAR1}->[0]" - not sure why?

    As BillKSmith mentioned, what's going on here is that you have one hash %lag_detail, and every time you push a reference to that hash onto the array, you're pushing a reference to the same one hash. This also means that when you write %lag_detail = ( ... );, you're resetting that one hash, and therefore every reference to the hash will reflect that change, which is unlikely to be what you want. That's what ${$VAR1}->[0] means: Data::Dumper is trying to tell you that the other elements of the array refer to the exact same data structure.

    In essence, this is what's going on - note how both times, a reference to the same hash is pushed onto the array:

    use warnings; use strict; use Data::Dumper; my @array; my %hash = ( abc=>123 ); push @array, \%hash; %hash = ( def=>456 ); push @array, \%hash; print Dumper(\@array); print $array[1]{def}, "\n"; # ok print $array[0]{abc}, "\n"; # nope! hash was cleared! __END__ $VAR1 = [ { 'def' => 456 }, $VAR1->[0] ]; 456 Use of uninitialized value in print at test.pl line 13.

    There are a couple of ways to fix this. The one that I think is probably most applicable in this case is to change from using a hash %lag_detail to a reference to an anonymous hash (hashref), as in:

    my $lag_detail; foreach my $line ( split( /\n/, $c ) ) { if ( $line =~ m/someregex/ ) { if ( exists $lag_detail->{ports} ) { push( @{ $self->{lags} }, $lag_detail ); } $lag_detail = { lagid => $lag, lagdesc => $4, operationstate => $3, adminstate => $2 }; } }

    The reason this works is that with $lag_detail = { ... }, you're actually allocating a new anonymous hash every time and getting a reference to it. See perlreftut and perlref if you're unsure about some of the details of references.

    Just for completeness, in addition to the (shallow) copying of the hash that BillKSmith suggested, a very common solution to this problem is to define the hash inside the loop, which also gets you a new hash on every iteration of the loop:

    foreach my $line ( split( /\n/, $c ) ) { my %lag_detail; ...

    However, since it looks to me like you're probably parsing a multiline data strucutre and don't want to push a record onto the array until it is complete, I think this is unlikely to be the correct fix in your case.

Re: Push array to hash
by AnomalousMonk (Bishop) on Jun 24, 2020 at 20:56 UTC

    Note that you have two very similar statements in the OPed code that are doing very different things:

    my %lag_detail = ...; # single instance of this hash in scope ... foreach my $line (...) ) { if (...) { if (...) ) { # push ref. to OLD instance of a hash to anon. array in an +other hash. push( @{ $self->{"lags"} }, \%lag_detail ); } # change certain values in hash, but hash LOCATION does not ch +ange. %lag_detail = ( "lagid" => $lag, "lagdesc" => $4, "operationst +ate" => $3, "adminstate" => $2 ); } } if (...) { # create NEW instance of a hash in independent scope. # this instance is TOTALLY INDEPENDENT of any other created in thi +s scope. my %port_detail = (...); # push ref. to NEW instance to anon. array in another hash. push @{ $lag_detail{"ports"} }, \%port_detail; } }
    References be tricky.


    Give a man a fish:  <%-{-{-{-<

Re: Push array to hash
by clueless newbie (Deacon) on Jun 24, 2020 at 20:10 UTC

    Set $Data::Dumper::Deepcopy to true before dumping.

    #!/usr/bin/env perl use Data::Dumper; use strict; use warnings; my %hash=("one two"=>"buckle my shoe","three four"=>"shut the door"); my @arrayOfHashes; for (1..3) { push @arrayOfHashes,\%hash; } warn 'BEFORE: ',Data::Dumper->Dump([\@arrayOfHashes],[qw(*arrayOfHashe +s)]),' '; $Data::Dumper::Deepcopy=1; warn 'AFTER: ',Data::Dumper->Dump([\@arrayOfHashes],[qw(*arrayOfHashes +)]),' ';
    BEFORE: @arrayOfHashes = ( { 'one two' => 'buckle my shoe', 'three four' => 'shut the door' }, $arrayOfHashes[0], $arrayOfHashes[0] ); at DataDumperDeepCopy.t line 12. AFTER: @arrayOfHashes = ( { 'one two' => 'buckle my shoe', 'three four' => 'shut the door' }, { 'one two' => 'buckle my shoe', 'three four' => 'shut the door' }, { 'one two' => 'buckle my shoe', 'three four' => 'shut the door' } ); at DataDumperDeepCopy.t line 14.
      Set $Data::Dumper::Deepcopy to true before dumping.

      Caveat: That only changes the way the data structure is displayed, but not the underlying data structure, that will still contain multiple references to the same hash.

      Icky

      warn 'BEFORE: ',Data::Dumper->Dump([\@arrayOfHashes],[qw(*arrayOfHash +es)]),' '; $Data::Dumper::Deepcopy=1; warn 'AFTER: ',Data::Dumper->Dump([\@arrayOfHashes],[qw(*arrayOfHashes +)]),' ';

      ok

      warn 'BEFORE: ',Data::Dumper->Dump([\@arrayOfHashes],[qw(*arrayOfHash +es)]),' '; warn 'AFTER: ',Data::Dumper->new([\@arrayOfHashes],[qw(*arrayOfHashes) +])->Deepcopy(1)->Dump,' ';

      ok? 😌

Re: Push array to hash
by bartrad (Beadle) on Jun 25, 2020 at 10:54 UTC

    You're all the best, thank you! I'd have never figured that out.

    Fix for me was to change %lag_detail to $lag_detail.

Re: Push array to hash
by perlfan (Priest) on Jun 24, 2020 at 21:11 UTC
    Assuming your real $line =~ m/somemoreregex/ has some captures in it? Also use strict; use warnings; and see what you get.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://11118431]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (5)
As of 2020-07-06 08:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?