5

I have a very noob-ish question here regarding refs, though still confounding to me at the very least...
In the code example below, i'm trying to create a hash of arrays:

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;
use Data::Dumper;

$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Terse = 1;
$Data::Dumper::Quotekeys = 0;

my @a1 = ( 'a1', 1, 1, 1 );
my @a2 = ( 'a2', 2, 2, 2 );

my $a1_ref = \@a1;
my $a2_ref = \@a2;

my @a = ( $a1_ref, $a2_ref );

my %h = ();

for my $i ( 1 .. 2 ) {
        $h{"$i"} = \@a;
}

say Dumper \%h;

The Dumper output is

{
          '1' => [
                   [
                     'a1',
                     1,
                     1,
                     1
                   ],
                   [
                     'a2',
                     2,
                     2,
                     2
                   ]
                 ],
          '2' => $VAR1->{'1'}
        }

The question here is:
why is $h{'2'} a reference to $h{'1'}? I'm trying to create a hash %h with identical key-values made of the @a array-of-arrays. I want each key-value of the hash to have it's own AoA based on @a, but i'm getting references to $h{'1'} instead. What am i doing wrong??
The Dumper output i'm trying to achive is:

{
          '1' => [
                   [   
                     'a1',
                     1,  
                     1,  
                     1   
                   ],  
                   [   
                     'a2',
                     2,  
                     2,  
                     2   
                   ]   
                 ],  
          '2' => [
                   [   
                     'a1',
                     1,  
                     1,  
                     1   
                   ],  
                   [   
                     'a2',
                     2,  
                     2,  
                     2   
                   ]   
                 ]   
        }   

Any help appreciated. thanks in advance!
-dan

Gnowl
  • 95
  • 4

4 Answers4

4

It's not that $h{'2'} is a reference to $h{'1'}, but that both are references to the same array, namely @a. What you probably want is:

for my $i ( 1 .. 2 ) {
    $h{"$i"} = $a[$i - 1];
}

which is equivalent to this:

$h{'1'} = $a[0];   # i.e., $a1_ref
$h{'2'} = $a[1];   # i.e., $a2_ref

which makes $h{'1'} a reference to @a1 and $h{'2'} a reference to @a2.

Incidentally, you might find it helpful to use the notations [ ... ] and { ... } to create references to anonymous arrays and hashes (respectively). Since you never use @a1 and @a2 except via $a1_ref and $a2_ref, you might as well create the latter directly:

my $a1_ref = [ 'a1', 1, 1, 1 ];   # reference to a new array (no name needed)
my $a2_ref = [ 'a2', 2, 2, 2 ];   # ditto

Edited for updated question: To copy an array, you can write:

my @orig = (1, 2, 3);
my @new = @orig;

or:

my $orig_ref = [1, 2, 3];
my $new_ref = [@$orig_ref]; # arrayref -> array -> list -> array -> arrayref

In your case, if I understand you correctly, you need to perform a slightly "deep" copy: you don't just want two arrays with the same elements, you want two arrays whose elements are references to distinct arrays with the same elements. There's no built-in Perl way to do that, but you can write a loop, or use the map function:

my @orig = ([1, 2, 3], [4, 5, 6]);
my @new = map [@$_], @orig;

So:

for my $i ( 1 .. 2 ) {
    $h{"$i"} = [map [@$_], @a];
}
ruakh
  • 175,680
  • 26
  • 273
  • 307
  • Thanks @ruakh, but that's not exactly what i was trying to achieve. I've updated my post with more info. that's a good tip regarding the reference notations. – Gnowl May 04 '13 at 19:58
2

This (what you are doing) will make $h{1} and $h{2} refer to the same array, @a:

    for my $i ( 1 .. 2 ) {
        $h{"$i"} = \@a;
    }

This will make $h{1} and $h{2} refer to two different objects, each one being a copy of @a:

    for my $i ( 1 .. 2 ) {
        $h{"$i"} = [ @a ];
    }

But the inner arrays will still be aliased. It sounds like you want a deep copy:

Community
  • 1
  • 1
Oktalist
  • 14,336
  • 3
  • 43
  • 63
2

I think this code does what you want.

I have switched to Data::Dump because I prefer its output.

The problem with taking the reference of data is that, no matter how many times you copy that reference, it still points to the same data, so a change in that data is relected everywhere the it is referred to.

To produce a second, independent copy of, say, an array, you can of course write my @copy = @array and work from there. But it is convenient to use [ @array ] which copies the contents of the array to a new anonymous array and returns a reference to it.

You want each hash value to be a reference to a two-element array, each of which is a reference to another array containing the data from @a1 and @a2. This code will do that for you.

Another point is that, since hash keys are strings, it is unusual to use numeric values for them: it indicates that you should be using an array instead. But since all of this is clearly placeholder data I am not too worried about it.

use strict;
use warnings;

use Data::Dump;

my @a1 = qw/ a1 1 1 1 /;
my @a2 = qw/ a2 2 2 2 /;

my %h;

for my $i ( 1, 2 ) {
  $h{$i} = [ [ @a1 ], [ @a2 ] ];
}

dd \%h;

output

{
  1 => [["a1", 1, 1, 1], ["a2", 2, 2, 2]],
  2 => [["a1", 1, 1, 1], ["a2", 2, 2, 2]],
}
Borodin
  • 126,100
  • 9
  • 70
  • 144
0

All the answers above pointed me to the correct solution (and the issue). And that's the need to make a copy of the structure and then use the reference to it. The arrays and hashes i pointed out here was just placeholder for a more complicated structure. So to make a copy of the structure i used the Clone module, which does exactly a deep copy of a the structure and return a different reference to it. Thanks all for the replies!

Gnowl
  • 95
  • 4