http://www.perlmonks.org?node_id=1209036

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

I am a beginner Perl student. I want to find different ways to do loops and modify an array from a function. So, here is what I have written so far. The last one ChangeArray6() doesn't work, and I don't know why. Also, what's the best way to deal with arrays? If my sub needs to change an array's contents, how should I write my code? What's the neatest, best, most memory-efficient way to do it from a sub?

use strict; use warnings; my @A = (0) x 20; PrintA(); ChangeArray1(); PrintA(); ChangeArray2(@A); PrintA(); ChangeArray3(@A); PrintA(); ChangeArray4(@A); PrintA(); ChangeArray5(\@A); PrintA(); ChangeArray6(@A); PrintA(); # # This function changes an array # sub ChangeArray1 { my $i = 0; LOOP: return if ($i >= @A); $A[$i++] = 1; # Write to global array goto LOOP; } sub ChangeArray2 { my $REF = \@_; # get reference for (my $i = 0; $i < @$REF; $i++) { $$REF[$i] = 2; } } sub ChangeArray3 { foreach my $X (@_) # $X = reference to each item { $X = 3; } } sub ChangeArray4 { my $i = @_; while ($i-- > 0) { $_[$i] = 4; # use direct reference } } sub ChangeArray5 { my $B = shift; # I don't understand this, but it works :P foreach my $X (@$B) { $X = 5; } } sub ChangeArray6 { @_ = (6) x @_; # This does not work. } sub PrintA { print "\n".join('', @A); }

Replies are listed 'Best First'.
Re: Changing an array from a sub
by tybalt89 (Monsignor) on Feb 13, 2018 at 02:40 UTC
    sub ChangeArray6 { @_[0..$#_] = (6) x @_; # Try this instead }
      Oh, Thank you!! It works!!!
Re: Changing an array from a sub
by NetWallah (Canon) on Feb 13, 2018 at 06:52 UTC
    Your call to
    ChangeArray6(@A);
    Passes the elements of @A into the subroutines @_.

    Your implementation of "sub ChangeArray6" updates the contents of @_ which were previously aliased to the contents of @A.

    Note: Only the contents of @_ were changed. The contents of @A were not.

    Here are two alternative ways to update the contents of @A:

    perl -E '@a=(2)x2; @a=ch(@a);sub ch{@_=(6) x @_}; say for @a'
    In the sample above, the change made to @_ is RETURNED, then ASSIGNED to @a, changing @a.

    perl -E '@a=(2)x2; ch(\@a);sub ch{@{$_[0]}=(6) x scalar @{$_[0]}}; say + for @a'
    In the second piece of code, we pass a REFERENCE to @a. The subroutine assigns new content to the thing(array ref-de-referenced) that it's first parameter referenced (@a).

    See 'perldoc perlref'.

                    Python is a racist language what with it's dependence on white space!

Re: Changing an array from a sub
by Eily (Monsignor) on Feb 13, 2018 at 17:59 UTC

    Since you already seem to know how references work, you can use them to get a clearer picture of what is happening.

    use strict; use warnings; use feature 'say'; my @array = (1,2); say '@array is: ', \@array; say ' - ', \$array[0]; say ' - ', \$array[1]; say "Iterating over the elements:"; say ' - ', \$_ for @array; MyFunction(@array); sub MyFunction { say "In MyFunction"; say '@_ is: ', \@_; say ' - ', \$_[0]; say ' - ', \$_[1]; say "Slice"; @_[0,1] = (3,4); say ' - ', \$_[0]; say ' - ', \$_[1]; say "Array overwrite"; @_ = (5,6); say ' - ', \$_[0]; say ' - ', \$_[1]; }
    @array is: ARRAY(0x6f66a0) - SCALAR(0x81ee20) - SCALAR(0x81ee38) Iterating over the elements: - SCALAR(0x81ee20) - SCALAR(0x81ee38) In MyFunction @_ is: ARRAY(0x724630) - SCALAR(0x81ee20) - SCALAR(0x81ee38) Slice - SCALAR(0x81ee20) - SCALAR(0x81ee38) Array overwrite - SCALAR(0x81f3c0) - SCALAR(0x81f3f0)
    You can see that also @_ and @array are different (stored in separate locations), they contain exactly the same elements (you can imagine @_ as containing links to the elements of @A (actually called alias), and overwriting @_ overwrites those links (Although the @_[...] syntax accesses several, possibly all elements of an array, it's still an access to the contained elements, not the array itself). You can also see that $_ is an alias to the elements of the list.

    Actually why the elements are aliased and not the array comes from the fact that lists are "flattened", meaning that containers are actually replaced by their contained elements. Eg in:

    @a = (1,2); @b = (3,4); (@a,@b); # my list
    The list is actually ($a[0], $a[1], $b[0], $b[1]) and the fact that those elements were in two different arrays isn't translated.

Re: Changing an array from a sub
by Marshall (Canon) on Feb 13, 2018 at 07:55 UTC
    Of your test cases, I think this one is the most important to understand:
    use strict; use warnings; my @A = (0) x 20; PrintA(); ChangeArray5(\@A); PrintA(); sub ChangeArray5 { my $B = shift; # I don't understand this, but it works :P foreach my $X (@$B) { $X = 5; } } sub PrintA { print "\n".join('', @A); } __END__ Prints: 00000000000000000000 55555555555555555555
    $B is a reference to the @A array. When it is de-referenced, you get an alias to the original array - not a copy of the original array - you are accessing the original array itself. Huge arrays can be passed to subroutines and modified in this way.

    If you want to operate upon a copy, then my @copy = (@$B) and iterate over @copy.
    I guess as a side note, $a and $b have special meanings in Perl. I wouldn't use $A or $B due to possible confusion.

Re: Changing an array from a sub
by corenth (Monk) on Feb 13, 2018 at 17:17 UTC
    First, I just want to state for the record that this is an awesome question. Did the "homework" then presented the findings. I think folk here appreciate that kind of thoughtfulness and interest in learning. Second, my favorite tool for learning new ways to do things in Perl is Data::Dumper.
    use Data::Dumper; my @array = #something or other . . . print Dumper @array; print Dumper \@array; #for a different form of output (array reference +) print Dumper $alsoanarrayreference; print Dumper $PrettyMuchAnything #because the output can be enlighteni +ng
    $state{tired}?sleep(40):eat($food);
Re: Changing an array from a sub
by jwkrahn (Abbot) on Feb 13, 2018 at 06:57 UTC
    sub ChangeArray1 { my $i = 0; LOOP: return if ($i >= @A); $A[$i++] = 1; # Write to global array goto LOOP; }

    That may be a "loop" in Assembler but it is technically not a "loop" in Perl. A proper perl loop would look something like this:

    sub ChangeArray1 { my $i = 0; LOOP: { $A[ $i ] = 1; redo LOOP if ++$i < @A; } }

      Personally I prefer this formulation (with the same caveat about modifying the global):

      sub ChangeArray1 { $_ = 1 for @A; }

      which to me is both clear and concise. All subjective, of course.

      And for the benefit of the OP: Go To Statement Considered Harmful.

Re: Changing an array from a sub
by rizzo (Curate) on Feb 14, 2018 at 05:30 UTC

    'Also, what's the best way to deal with arrays?'

    Probably not always the best way, but sometimes handy comes the map-function:

    map - perldoc.perl.org

      Thank you very much for all your answers! I come from a JavaScript background, and in JavaScript when you make a function call, a copy of each variable is passed to the function, but if the variable is an array, then only a reference is passed. So, writing to that array will change the original array as well. It's confusing when you combine it with perl. Lol But I am trying to sort it out.
      // The only way to work with arrays in JavaScript: myArray = [0, 1, 2, 3, 4]; ChangeArrayJS(myArray); function ChangeArrayJS(ARRAY) { ARRAY[2] = 'JS'; // changes myArray var COPY_OF_ARRAY = ARRAY; COPY_OF_ARRAY[2] = 'this will be lost'; }