Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Copy of multidimensional hash

by tommaso.fornaciari (Initiate)
on Jan 03, 2013 at 08:50 UTC ( #1011418=perlquestion: print w/ replies, xml ) Need Help??
tommaso.fornaciari has asked for the wisdom of the Perl Monks concerning the following question:

Dear Perl Monks,
this is my first post, so sorry if I am wrong in some way.
I am working with multidimensional hashes, and I get an output which seems to me quite weird.
In particular:
- I create a multidimensional hash;
- I create a copy of this hash;
- I modify that copy.
When I print, it seems that both hashes were modified, and I do not understand why. With monodimensional hashes, everything is normal instead.
Please consider the following script and the output I receive:

#!/usr/bin/env perl use Modern::Perl 2011; use autodie; # I create a multidimensional hash my %one = ('a' => [1, 2], 'b' => [3, 4], 'c' => [5, 6]); # I create a copy: my %two = %one; # I print them: say "Before:\n\%one:"; for(sort keys %one){say "$_\t$one{$_}->[0]\t$one{$_}->[1]"} say '%two:'; for(sort keys %two){say "$_\t$two{$_}->[0]\t$two{$_}->[1]"} # Then I modify the copy: for(sort keys %two){$two{$_}->[1] = 'two'} # And I print again: say "After:\n\%one:"; for(sort keys %one){say "$_\t$one{$_}->[0]\t$one{$_}->[1]\tWhy???"} say '%two:'; for(sort keys %two){say "$_\t$two{$_}->[0]\t$two{$_}->[1]"} # I receive this output: #Before: #%one: #a 1 2 #b 3 4 #c 5 6 #%two: #a 1 2 #b 3 4 #c 5 6 #After: #%one: #a 1 two Why??? #b 3 two Why??? #c 5 two Why??? #%two: #a 1 two #b 3 two #c 5 two

Thanks for any explanation you could give me!
Tommaso

Comment on Copy of multidimensional hash
Download Code
Re: Copy of multidimensional hash
by davido (Archbishop) on Jan 03, 2013 at 08:57 UTC

    Multi-dimensional data structures in Perl use references. Each of your hash keys indexes a value. That value is a reference to an anonymous array. When you copy the hashes, the new one still holds the same references, to the same anonymous arrays.

    You want to look at Storable's dclone() function: It makes a deep recursive copy of the values rather than a shallow copy of the references.


    Dave

Re: Copy of multidimensional hash
by johngg (Abbot) on Jan 03, 2013 at 09:02 UTC
    I believe it is because your %two hash contains as values references to the same anonymous arrays as are in %one. You would have to do something like (not tested)

    my %two; foreach my $key ( keys %one ) { $two{ $key } = [ @{ $one{ $key } } ]; }

    in order to create values which are new anonymous arrays containing the same contents as those in the original hash.

    I hope this is helpful.

    Cheers,

    JohnGG

Re: Copy of multidimensional hash
by Anonymous Monk on Jan 03, 2013 at 09:03 UTC
    References are reference :) a multidimensional hash stores references, a hash of arrays stores array references as values(hash value) associated with hash-keys, and the same ones refers to the same value
    $ perl -le " %F =(1, [41] ); $F{2}= $F{1}; print for %F; " 1 ARRAY(0x3f9074) 2 ARRAY(0x3f9074)

    value of 1 is same as 2, same reference ARRAY(0x3f9074), same array, so  $F{2}[0]++ makes  $F{1}[0] 42, because $F{1} and $F{2} refer to the same array

    See Tutorals: References, and perlref

      Dear All Monks,
      Thanks a lot for your quick and clear answers!
      In fact, storing elsewhere the same data, it works:

      #!/usr/bin/env perl use Modern::Perl 2011; use autodie; # I create a multidimensional hash my %one = ('a' => [1, 2], 'b' => [3, 4], 'c' => [5, 6]); # I create a copy: # wrong way: #my %two = %one; # right way: my %two; for(keys %one) {$two{$_}[0] = $one{$_}[0]; $two{$_}[1] = $one{$_}[1]} # I print them: say "Before:\n\%one:"; for(sort keys %one){say "$_\t$one{$_}[0]\t$one{$_}[1]"} say '%two:'; for(sort keys %two){say "$_\t$two{$_}[0]\t$two{$_}[1]"} # Then I modify the copy: for(sort keys %two){$two{$_}[1] = 'two'} # And I print again: say "After:\n\%one:"; for(sort keys %one){say "$_\t$one{$_}[0]\t$one{$_}[1]\tOk!!!"} say '%two:'; for(sort keys %two){say "$_\t$two{$_}[0]\t$two{$_}[1]"} # I receive this output: #Before: #%one: #a 1 2 #b 3 4 #c 5 6 #%two: #a 1 2 #b 3 4 #c 5 6 #After: #%one: #a 1 2 Ok!!! #b 3 4 Ok!!! #c 5 6 Ok!!! #%two: #a 1 two #b 3 two #c 5 two

      Is this the best way to create a copy of a multidimensional hash?
      Thanks again for your helpfulness!
      Tommaso

        Is this the best way to create a copy of a multidimensional hash?

        I don't know if it's the best. At any rate, I would reformat the for loop as follows:

        for (keys %one) { $two{$_}[0] = $one{$_}[0]; $two{$_}[1] = $one{$_}[1]; }

        I find this significantly more readable than the way you formatted it. Furthermore, I would generalize copying the arrray, and tell Perl to take out all the elements instead of just those elements with index 0 and 1.

        for my $k (keys %one) { $two{$k}[$_] = $one{$k}[$_] for 0..scalar( @{$one{$k}} ); }

        And then I would generalize it even further: take the whole array reference, dereference it in a single pass, and create and store a new reference to it. Sounds complex? Nah, not so much:

        for my $k (keys %one) { $two{$k} = [ @{$one{$k}} ]; }

        This was already suggested by johngg.</p

        Step by step of [ @{$one{$k}} ]:

        1. $one{$k}: this takes the element identified by key $k from hash %one. That element, in this case, is an array reference.
        2. @{ ... } dereferences the array reference inside the curlies. So after @{ $one{$k} } we're working with a normal, regular, every day array (albeit an anonymous one).
        3. [ ... ] stores whatever is between the square braces as an anonymous array and returns a reference to it.

        But then I would want to generalize it even further and use Storable's dclone function, as suggested by davido.

        use Storable qw(dclone); ... my %two = %{ dclone(\%one) } # dclone() takes a ref and returns a r +ef # So we'll have to put in \%one (not % +one) # and then we have to dereference ( %{ +...} ) # what comes out.
        You could use Clone and copy the hash using my %two = %{ Clone::clone(\%one) };
        Or use hashrefs all the way:
        my $hr_one = { 'a' => [1, 2], 'b' => [3, 4], 'c' => [5, 6]}; my $hr_two = Clone::clone($hr_one);
Re: Copy of multidimensional hash
by tobyink (Abbot) on Jan 03, 2013 at 09:42 UTC

    You are not using true multidimensional hashes. Perl doesn't have true multidimensional hashes. However, it offers two ways to emulate multidimensional structures.

    One way is to use references; this is the way you're currently doing it. A reference is like a link or a pointer. When you copy the outer structure, you're just copying a collection of links to the inner structures. The new copy is still pointing at the same inner structures, so altering the inner structures will affect both outer structures.

    Perl does offer another mechanism for emulating multi-dimensional hashes. It is barely documented (mentioned in perlvar) and rarely used, but actually works quite well in some situations.

    use strict; use warnings; use Data::Dumper; local $; = '#'; my %hash1; $hash1{'Hello', 'World'} = 1; $hash1{'Hello', 'Pluto'} = 2; $hash1{'Goodbye', 'World'} = 3; $hash1{'Goodbye', 'Pluto'} = 4; my %hash2 = %hash1; # copy $hash2{'Goodbye', 'Pluto'}++; # alter hash 2 print "$hash2{'Goodbye', 'Pluto'}\n"; # show that it is altered print "$hash1{'Goodbye', 'Pluto'}\n"; # hash1 remains unaltered # Let's see what's going on here... print Dumper(\%hash1, \%hash2);
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

      Dear Tobyink,
      thanks to you too for the nice tip :))

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1011418]
Approved by johngg
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (6)
As of 2014-09-03 06:23 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite cookbook is:










    Results (35 votes), past polls