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

This foreach() will run several times, each time populating an array and putting a reference to it in a second array. When it finishes, however, all the references are to the same array (the last one to populate). I guess I could just split the array, pass it, then join it again, but that seems so bush league. Is there a better way?
my $doc_num; my $tmp_doc_num; my @doc_nums; my @doc; foreach( @doc_nums ) { @doc = (); $doc_num = substr( $_, 0, 7 ); foreach( @raw ) { @fields = split( /,/, $_ ); if( $fields[0] eq "AIP65" ) { $tmp_doc_num = substr( $fields[5], 0, 7 );; } else { $tmp_doc_num = substr( $fields[4], 0, 7 ); } ( $tmp_doc_num eq $doc_num ) ? push( @doc, $_ ) : next; } push( @docs, \@doc ); }
Thanks in advance, Casey

Replies are listed 'Best First'.
Re: Reference Question
by Tanktalus (Canon) on Aug 03, 2006 at 21:41 UTC

    That's because you're reusing the same array each time through the loop. There's only one @doc - the one you declared outside the loop. You push a bunch of references to that array onto the @docs list, but since it's the same @doc, they'll all be the same.

    Instead, try one of these idioms. My favourite is to just declare @doc inside your loop:

    foreach( @doc_nums ) { my @doc;
    This will create a new array each time. That allows the push to get a different reference. And since Perl does reference counting right, those arrays, though out of scope at the end of the loop, won't be cleared away because the @docs array still refers to them.

    Second option: create a copy of the @doc array, and push that on to the list:

    push @docs, [ @doc ];
    This will be a bit slower if @doc can be large, but is nice that it's explicitly obvious. That said, the first option is such a common idiom that it's pretty obvious after a while, too.

    Hope that helps :-)

Re: Reference Question
by GrandFather (Saint) on Aug 03, 2006 at 21:41 UTC

    Move my @doc inside the loop so you get a new instance of the doc array each time through. Consider:

    use strict; use warnings; my @doc_nums = qw(1 2 3 4 5 6 7 8 9); my @docs; foreach( @doc_nums ) { my @doc; push @doc, int rand 10 for 1..3; push @docs, \@doc; } print "@$_\n" for @docs;

    Prints:

    9 5 8 9 5 7 2 2 1 3 0 4 8 6 4 6 1 7 6 5 5 8 6 9 9 9 7

    or you could change the reference to a copy:

    use strict; use warnings; my @doc_nums = qw(1 2 3 4 5 6 7 8 9); my @docs; my @doc; foreach( @doc_nums ) { @doc = (); push @doc, int rand 10 for 1..3; push @docs, [@doc]; } print "@$_\n" for @docs;

    DWIM is Perl's answer to Gödel
Re: Reference Question
by Hue-Bond (Priest) on Aug 03, 2006 at 21:43 UTC
        push( @docs, \@doc );

    You need push( @docs, [ @doc ] );. The reason is that if you use a backslash, you are taking a reference to the same variable, which contains differents things each time but the reference "points to" the same location in memory. Using square brackets, you create a new reference each time. That's the difference.

    Update: To make things clearer, look at this program and its output:

    The first time, @d is populated with references to the same information (Data::Dumper shows $VAR1->[0] instead of repeating it). The second example shows different numbers because [ ] create different references each time.

    --
    David Serrano

      Actually you create a copy each time with the [], and push a reference to the copy.

      In the original code the push was creating a new reference each time - to the same instance.


      DWIM is Perl's answer to Gödel
      Perfect! Thanks a ton. For my own edification, do the square brackets actually copy the array *and* make a reference to it in one fell swoop?

        The [] makes an anonymous copy of the array and in effect returns an reference to it. {} can be used in similar fashion to create an anonymous copy of a hash.


        DWIM is Perl's answer to Gödel
Re: Reference Question
by planetscape (Chancellor) on Aug 03, 2006 at 23:50 UTC