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

I have a real pickle of a problem here and I'm not even sure how to googlefoo this one. I have an array reference. I am pushing hashrefs onto it. These arrays are being cloned if they are substantially *similar*. If they are different ENOUGH, a new, unique, hashref is pushed onto the arrayref. If they are too similar, then they are cloned? I'm not even really sure about *how* different the hashrefs need to be before the elements are not cloned.

Here is some code:

#!/usr/bin/perl use strict; my $Action; my $piece; $piece->{this} = 123; $piece->{that} = 'some text'; foreach my $other (111...113){ $piece->{theother} = $other; push(@{$Action}, $piece); } use Data::Dumper; print Dumper($Action); foreach my $section (@{$Action}){ print Dumper($section) . "\n"; }

What I EXPECT to get out of this is an arrayref of hashreferences that are slightly different (different values for 'theother' key).

$Action->[ { this => 123, that => 'some text', theother => 111 }, { this => 123, that => 'some text', theother => 112 }, { this => 123, that => 'some text', theother => 113 } ]

What the actual output is:

$VAR1 = [ { 'that' => 'some text', 'theother' => 113, 'this' => 123 }, $VAR1->[0], $VAR1->[0] ]; $VAR1 = { 'that' => 'some text', 'theother' => 113, 'this' => 123 }; $VAR1 = { 'that' => 'some text', 'theother' => 113, 'this' => 123 }; $VAR1 = { 'that' => 'some text', 'theother' => 113, 'this' => 123 };

What is going on here? This seems like some sort of memory optimization gone wrong. Is there any way I can 'force' perl to *do what I mean* in this case?

Replies are listed 'Best First'.
Re: Unwanted cloning of array elements
by hippo (Archbishop) on May 07, 2021 at 10:26 UTC

    $piece is a reference. You push the same reference onto your arrayref 3 times. It does not matter when or how you change some sub-part of the thing the reference points to. If you want 3 different elements in your arrayref then you are going to have to deep-copy $piece at every push.


    🦛

      Hippo! Thank you so much for that link on deep-copy mechanics. That's exactly what I needed :)

      Fixed code

      #!/usr/bin/perl use strict; my $Action; my $piece; $piece->{this} = 123; $piece->{that} = 'some text'; foreach my $other (111...113){ my $thispiece = {%$piece}; # create a deep copy $thispiece->{theother} = $other; push(@{$Action}, $thispiece); } use Data::Dumper; print Dumper($Action);

      And now the output looks like I expected it to:

      $VAR1 = [ { 'that' => 'some text', 'theother' => 111, 'this' => 123 }, { 'theother' => 112, 'that' => 'some text', 'this' => 123 }, { 'this' => 123, 'theother' => 113, 'that' => 'some text' } ];

      Hippo FTW!

        my $thispiece = {%$piece};  # create a deep copy

        Be aware that {%$piece} creates a shallow copy. It may appear to be deep, but only because $piece is shallow to begin with, i.e., it has only one level. Try your code with a nested structure for $piece:

        Win8 Strawberry 5.8.9.5 (32) Fri 05/07/2021 7:11:33 C:\@Work\Perl\monks >perl -Mstrict -Mwarnings my $Action; my $piece = { this => { that => { foo => { bar => 123 } } } }; foreach my $other (111 .. 113){ my $thispiece = {%$piece}; # create a SHALLOW copy $thispiece->{theother} = $other; push(@{$Action}, $thispiece); } use Data::Dumper; print Dumper($Action); ^Z $VAR1 = [ { 'theother' => 111, 'this' => { 'that' => { 'foo' => { 'bar' => 123 } } } }, { 'theother' => 112, 'this' => $VAR1->[0]{'this'} }, { 'theother' => 113, 'this' => $VAR1->[0]{'this'} } ];
        Of course, if you're only dealing with a shallow structure in $piece to begin with, your code will be fine.


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

Re: Unwanted cloning of array elements
by bliako (Abbot) on May 07, 2021 at 11:35 UTC

    A "reference" means a reference to a space in (perl interpreter) memory. It's like the number of a pigeon-hole/post-office box: the number(=address) remains the same but the contents may change any time. And that change will be reflected on all those places in your script where you read that reference. For example:

    my @arr = (1,2,3); my $ref = \@arr; # or just [1,2,3] print $ref->[0]; # 1 $arr[0] = 42; print $ref->[0]; # 42

    No matter which part of your script you pass that reference, when you do $arr[0] = 42; all these places will automatically see the new value 42. Because you gave them a pigeon-hole (PH) number. They look for the contents of that PH. And when you change the contents, they will see that change.

    In the loop foreach my $other you keep changing the contents of just one PH you asked for, outside the loop. Its address remains the same, set in my $piece; So, as anonymonk says, create a new reference each time you loop. The PH will be preserved in memory and in your hash even when my $piece goes out of scope. Because you passed that PH number to your hash, it remains alive (here you may want to read Perl and Garbage Collection).

    This is a very powerful feature. You create a data structure in memory, like you do. Then get a reference (its PH number) and then pass that to any part of your (single-threaded) program, e.g. subs, which need to read or write onto that data structure. All changes will be communicated immediately to all clients.

    Here is some fun:

    use strict; use warnings; use Data::Dumper; my $joe; $joe->{name} = 'joe'; $joe->{age} = 42; my $mary; $mary->{name} = 'mary'; $mary->{age} = 43; my $peter; $peter->{name} = 'peter'; $peter->{age} = 100; my @children = ($joe, $mary); my @family = ($mary, $peter, $joe); my @males = ($joe, $peter); ## oops, Joe is actually written as John $joe->{name} = 'john'; print Dumper(@children); print Dumper(@family); print Dumper(@males);

    bw, bliako

Re: Unwanted cloning of array elements
by Anonymous Monk on May 07, 2021 at 10:42 UTC