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

Hi monks,

I'm creating a few types of arrays in each iteration of a loop, and saving them as references within a hash who's value is also an array ref.

foreach $file(@filenames){ open FILE, "$file"; while (<FILE>){ $array1 =(); $array2 = (); #fill @array1; array1_ref = \@array1; #fill @array2; array2_ref = \@array2; } $hash{$file} = [$array2_ref, $array1_ref] ) #accessing the info: print @$hash{$somefile}[1];

When I try to dereference to access the data, the arrays are empty, although I've verified that the references work within the foreach loop.

I think that this is because the references in the hash are pointing to something that gets zeroed and written over in each iteration of the loop. Is there a way to avoid giving new names to the arrays in each iteration, but still have them retain their memory address?

thanks, maayan

Replies are listed 'Best First'.
Re: saving nested arrays in a hash
by jethro (Monsignor) on Aug 26, 2010 at 16:22 UTC

    There are a a lot of bugs in your example code. Please add 'use warnings;' at the beginning, you will find out that a lot of errors get noticed that way. Also 'use strict;' is advisable

    To answer your question, the problem is that your array references always point to the same data structure. The reason is that you use global variables. To correct that you could either use 'my' to create a new variable every time, i.e. my @array1= ();, or you could make sure that you copy the values, i.e. $hash{$_} = [ [@array1], ... ];

    But your script has more problems than that. For example:

    $array1=();. The $ should be a @, if you had warnings on, perl would have told you

    array1_ref = \@array1;. A '$' is missing in front of the array1_ref. Again perl would have told you with warnings on

    Also the clearing of @array1 and @array2 should be before the while loop, if I get your intention right

    You could add a line 'use Data::Dumper;' to the beginning of your program and then for example print Dumper(\%hash); at the end to find out how the data structure looks in reality. Data::Dumper ist the best available debugging tool you can find in perl on short notice ;-)

      thanks! it works now. so what's the technical difference between $ref=\@something and $ref =[@something] ? sorry about the faulty example code... it's not copied from the actual script.. I just typed it here with the silly mistakes included

      my bad.

      :)

        Lets say @something is at memory location 0x12345

        $ref= \@something; print $ref; # would print 0x12345, right? $ref2= \@something; print $ref2; # would print 0x12345 as well

        Obvious, right? You take a reference to the array, it will be always the same. Now the other case:

        $ref= [ @something ]; # would be the same as $ref= \( @something ); # if that syntax where possible, or my @x= ( @something ); # with correct syntax $ref= \@x;

        i.e. [] is just () with an additional reference-taking. And by doing ( @array ) you are creating a new array (at a new memory address) with the contents of @array filled in

        ( @array ) is not the same as @array. This is obvious when you look at @newarray= ( @array1, @array2 );. It can't be the same

        $ref1 = \@arr; $ref2 = \@arr;

        The two references both point to the same underlying data structure.

        $ref1 = [ @arr ]; $ref2 = [ @arr ];

        The two references each point to different anonymous arrays, which have the same values as @arr, but are different structures.

        As Occam said: Entia non sunt multiplicanda praeter necessitatem.

Re: saving nested arrays in a hash
by Corion (Patriarch) on Aug 26, 2010 at 15:53 UTC

    Are you using strict in your real code? Doing so would reveal a lot of errors that you're making.

    array1_ref = \@array1;

    This is not even valid Perl.

    A reply falls below the community's threshold of quality. You may see it by logging in.
Re: saving nested arrays in a hash
by Marshall (Canon) on Aug 31, 2010 at 02:59 UTC
    I would recommend against excessive use of statements like this: $array1_ref = \@array1; Sometimes this is not necessary and just obscures the code's intent.

    A more typical thing would be like I show in Example 1. For data, I just made some random numbers to keep things simple.

    This code [@array1] means: create an array reference to some newly allocated memory which has no name (anonymous memory) and copy @array1 into that new memory. This is called a deep copy. From the printout, you will see that this works, The data in set B is different than the data in set A (nothing got overwritten). Note that the code does not contain any statements like: $array1_ref = \@array1;

    Example 2 is interesting. Whoa! Why did it work? It looks like it shouldn't but it did. The reason is the "my @array" declarations. When Perl runs across a "my" statement you are guaranteed to get a brand new one even if you've been past this statement before. If possible Perl will reuse memory. In this case it couldn't because it figures (correctly) that the previous memory is still in use because somebody has a reference to it (its in the hash table.). This is a case where I would normally use [ ] like in example 1. That does require some extra copying, but the clarity is usually "worth it", i.e. normally I would use syntax in Example 1.

    Example 3 is a case of completely botched up code. The my declaration is outside the loop and therefore the @array memory is getting re-used...there is no "my" declaration to "save our butt". the printout is a bit confusing to understand, but basically what it is saying is "hey, set Y clobbered set X.

    Data::Dumper is very helpful when debugging data structures and here I can see that there is something odd about this X,Y pair of data.

    #!/user/bin/perl -w use strict; use Data::Dump qw[pp]; $|=1; my %hash; print "** Example 1 ***\n"; #pretty normal thing to do for my $set('A'..'B') { my @array1 = map { int(rand(100)) }(1..2); my @array2 = map { int(rand(100)) }(1..5); $hash{$set} = [ [@array1], [@array2] ]; } pp \%hash; print "** Example 2 ***\n"; #a pitfall here for my $set('G'..'H') { my @array1 = map { int(rand(100)) }(1..2); my @array2 = map { int(rand(100)) }(1..5); $hash{$set} = [ \@array1, \@array2 ]; } pp \%hash; print "** Example ***3\n"; #the wrong thing to do my @x; my @y; for my $set('X'..'Y') { @x = map { int(rand(100)) }(1..2); @y = map { int(rand(100)) }(1..5); $hash{$set} = [ \@x, \@y ]; } pp \%hash; __END__ ** Example 1 *** { A => [[45, 13], [77, 53, 52, 21, 23]], B => [[38, 27], [4, 59, 52, 4, 83]], } ** Example 2 *** { A => [[45, 13], [77, 53, 52, 21, 23]], B => [[38, 27], [4, 59, 52, 4, 83]], G => [[3, 35], [13, 58, 35, 76, 64]], H => [[26, 39], [34, 91, 99, 74, 97]], } ** Example ***3 do { my $a = { A => [[45, 13], [77, 53, 52, 21, 23]], B => [[38, 27], [4, 59, 52, 4, 83]], G => [[3, 35], [13, 58, 35, 76, 64]], H => [[26, 39], [34, 91, 99, 74, 97]], X => [[31, 63], [72, 71, 10, 80, 76]], Y => ['fix', 'fix'], }; $a->{Y}[0] = $a->{X}[0]; $a->{Y}[1] = $a->{X}[1]; $a; }