Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

for, foreach and $_...

by Foggy Bottoms (Monk)
on Aug 18, 2003 at 13:59 UTC ( [id://284575]=perlquestion: print w/replies, xml ) Need Help??

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

I was happily coding my way through a program when out of the blue there came a glitch...

I believe they are 2 ways to write a for / foreach loop :
  • for (my $i=0;$i<@array;$i++) { $array[$i] = 3; # dummy value # do your stuff, $i being the counter }
  • foreach (0..@array) { $array[$_] = 3; # do your stuff, $_ being the counter }
However, in my bit of code, the second and third methods only work fine with the first element and fails with the others.

It then occured to me that foreach (0..@array) seemingly went from 0 to to the size of the array. I then reread some array info and realized that was it ! I'm quite bashful now... It's so obvious that an array name returns the actual array size...

Isn't there yet another way, something like
foreach(@array) { # use $_ as counter # do your stuff }

Thanks for your insight, David.

Replies are listed 'Best First'.
Re: for, foreach and $_...
by liz (Monsignor) on Aug 18, 2003 at 14:09 UTC
    This:
    foreach (0..@array)
    should probably read:
    foreach (0..$#array)
    as you want to access all of the current elements of the array, rather than add an extra one at the end (@array in scalar context is the number of element, whereas $#array is the ordinal number of the last element). And since arrays are (usually) 0-based, the highest numbered element is one less than the number of elements.

    If you do:

    foreach (@array)
    then $_ is aliased to the array element being processed. So everything you do to $_, you are actually doing to the array element.

    Hope this helps.

    Liz

Re: for, foreach and $_...
by larsen (Parson) on Aug 18, 2003 at 14:09 UTC
    foreach(@array) { # $_ is an alias, in turn, for $array[ 0 ], $array[ 1 ], ... $array[ + n ] }
    If you need a counter, try this:
    foreach ( 0 .. $#array ) { # $_ 's value will be 0, 1, ..., (last index of @array) }
Re: for, foreach and $_...
by broquaint (Abbot) on Aug 18, 2003 at 14:15 UTC
    The first example is the C style for loop with initialisation, condition and post-loop action in the preceding parens1. As for the second example that is the more natural way of iterating over lists in perl - it takes a list and aliases the topic ($_ by default) to each element per iteration. However in your second example, you create a list of values from 0 to the size of the array, as opposed to the last index of the array i.e
    for(0 .. $#array) { ... }
    This style is definitely the common way of iteratively accessing the elements of an array, but the more idiomatic approach of iterating over an array, when the index isn't needed, is simply
    for(@array) { ... }
    That works because an array in a list context flattens to a list (as opposed to in scalar context, where it yields the number of elements it contains, which is almost always $#array + 1) providing for with a list to iterate over.

    See. perlsyn for more info.
    HTH

    _________
    broquaint

    1 internally, I believe this is implemented as a while(COND) { ... } continue { ... }

      internally, I believe this is implemented as a while(COND) { ... } continue { ... }
      Yes, it is:
      lorenzo@alice:~$ perl -MO=Deparse,-x3 -e 'for( my $i = 0; $i < 10; $i+ ++ ) {}' while ($i < 10) { ; } continue { ++$i } -e syntax OK
Re: for, foreach and $_...
by davido (Cardinal) on Aug 18, 2003 at 17:11 UTC
    Your first example is a "C style" loop, as others have already pointed out. I might add that many feel there is usually a more elegant way to do it than a C style loop, thanks to the way foreach works.

    Your second example failed because you are counting from zero to size-of-array, rather than zero to last-element-of-array. This is causing you to loop one element past the end of the way. If you're not using warnings and strictures, Perl will just happily let you do it, but your results won't be what you're looking for. For a ".." list constructor to work in this case, you might want

    foreach ( 0..@#array ) {...

    instead, which constructs a list of numbers from zero to last-element-number-of-@array. The loop iterates over the list of numbers, and $_ can be used as a subscript to @array.

    But as you've deduced, there is yet a better way to do it:

    foreach ( @array ) {.....

    In that case, what you get is the loop iterating over the actual elements of the array. There is no need to read into the array yourself in the loop's block. Instead, $_ becomes an alias for $array[current-loop-iteration]. Any changes you make to $_, in this context, will write through to the current element of @array.

    The next tidbit is to understand that foreach and for are synonyms for each other in Perl's internals. Therefore, 'foreach ( @array )' can also be written as 'for ( @array )', and you will get the same results. For most people, myself included, it is usually a better practice to say foreach when you mean it, and for when you mean it, but the fact is that you can say potato and I can say potato (soft A), and Perl considers it the same thing.

    The next topic raised in this discussion thread is what happens to $_ in the following example:

    foreach ( @first_array ) { foreach ( @second_array ) { print "$_\n"; } }

    Specifically, the question was, can the inner loop access the current element of @first_array? No, not as the code is written. Think of $_ as being "my $_", with its own namespace for the current block. That inner loop is its own block, and $_ within that block refers to the current element of @second_array. But outside of the inner loop, either before the inner loop or after it, $_ refers to the outter loop's current element, as long as you're still inside the outter loop. ;) Got it? (Just read that last sentence twice and it will all be ok.)

    What you need, if you want to be able to refer back to the current element of the outter loop, from within the inner loop, is one of the following two constructs (Which differ in functionality. I'll describe that later).

    foreach my $outter_element ( @outter_array ) { foreach ( @inner_array ) { print "$outter_element, $_\n"; } }

    OR

    foreach ( @outter_array ) { my $outter_element = $_; foreach ( @inner_array ) { print "$outter_element, $_\n"; } }

    The two above examples function identically as the examples are presented. However, remember that within foreach loops, $_ IS A $array[current_element]. In other words, whatever you do to $_, within the foreach loop, modifies the current iteration's @array element. So in the first example, you can still modify the outter array's current element from within the inner loop. Whereas in the second example, you can only modify the inner array's current element, because in the second example, $outter_element is just a copy of the outter element's value.

    That's a mouthful. But it's a very useful distinction to understand.

    Also understand that in the case of 'for ( @array )' or 'foreach ( @array )', you don't have a counter, you just have the element itself in $_. If you want a counter, you'll have to establish one explicitly. But unless you explicitly *need* to know how many times you've looped, you can just not even worry about how many times you've looped, and let the script happily do its thing.

    Finally, I've seen mention in this thread of the following construct:

    print "$_\n" foreach @array;

    This is an elegant way of saying:

    foreach ( @array ) { print "$_\n"; }

    Use that construct when it makes the code more readable, but leave it alone if it seems to make the code more obscure. It's a style issue. There are perfectly legitimate examples of when each of those constructs might make for cleaner code.

    Have fun with loops! ;)

    Dave

    "If I had my life to do over again, I'd be a plumber." -- Albert Einstein

Re: for, foreach and $_...
by monktim (Friar) on Aug 18, 2003 at 14:07 UTC
    Yes, that is exactly right.
    use strict; use warnings; my @array = qw/one two three four five/; foreach (@array) { print "$_\n"; }
      What if you use that method twice one within the other as follows :
      foreach(0..@array) { foreach(0..@otherarray) { # if I use $_ here, what does it refer to ? } }
      Is there another system like $1, $2... ?
        ...Is there another system like $1, $2... ?

        Not in Perl 5. $_ always refers to the innermost loop. You will have to name the outer one specifically if you want to access it from the inner one.

        foreach my $outer (@array) { foreach (@otherarray) { } }

        I've been lead to understand that you will be able to access topics (Perl 6 speak for $_) "above" your current scope in Perl 6, but it will be considered bad programming practice to actually use it. It will be more for debugging tools that e.g. will allow you to show what the current topic is while you're stepping through the code.

        That's what I remember of various Perl 6 talks and reading about it... Of course, it may have all be changed again by now... ;-)

        Liz

Re: for, foreach and $_...
by bm (Hermit) on Aug 18, 2003 at 14:09 UTC
    Yes, you're right. Your last solution is probably preferable. You can also do things like  print for @array; which again uses $_.

    Isn't that nice? Sometimes it is obvious the Perl was conceived by a linguist. :-)
    --
    bm

Re: for, foreach and $_...
by bear0053 (Hermit) on Aug 18, 2003 at 14:13 UTC
    when using a foreach loop $_ contains the value on which you are looping. It is a shortcut so you don't actually need to say:
    foreach my $app (@apps){ #stuff with $app }
    and use more space with another variable in your program as minimal as that may seem when $_ or $app contains a value like '1'

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (6)
As of 2024-04-19 12:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found