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

Hi,

Often I need to do something on the first iteration of a loop (or not do something on the first iteration). What I end up doing is something of this nature:

# display_paragraph(array_ref, indentation) # # print lines of array, indenting all but first line. # Note: use strict and use warnings are in effect, # so must declare 'my' vars! # sub display_paragraph() { my $aref = shift; my $indentation = shift; my $first_time = 1; # declare and init a flag for my $line (@$aref) { if (!$first_time) { # test the flag print ' ' x $indentation; $first_time = 0; # change the flag } print "$line\n"; } }
Is there a way to do this less verbosely?

Replies are listed 'Best First'.
Re: Easiest way to do something only on first iteration of loop
by Mr. Muskrat (Canon) on May 06, 2016 at 22:35 UTC

    Marshall++ for pointing out the most obvious solution. However, since it is possible that you have simplified what you are trying to accomplish, here are three alternatives.You can decide which is best.

    sub display_paragraph() { my $aref = shift; my $indentation = shift; my $first_time = 0; # declare and init a flag for my $line (@$aref) { if (++$first_time == 1) { # change and test the flag print ' ' x $indentation; } print "$line\n"; } }
    use 5.010; sub display_paragraph() { my $aref = shift; my $indentation = shift; for my $line (@$aref) { state $first_time = 0; # declare and init a flag if (++$first_time == 1) { # change and test the flag print ' ' x $indentation; } print "$line\n"; } }
    sub display_paragraph() { my $aref = shift; my $indentation = shift; while (my ($index, $line) = each @$aref) { if ($index == 0) { # test the position print ' ' x $indentation; } print "$line\n"; } }
      My example was only meant to be illustrative -- it does seem that there are times I really need to deal with the first-time (or subsequent-time) condition inside the loop, rather than outside, to avoid duplicating code (also, loops often iterate zero times, in which case you don't want the action to be taken).

      I've tried a shorter version of #1, setting my $next_time = 1 and then testing for (!--$next_time) but that's sort of ridiculous.

      In your second alternative, with the 'state' variable, does the state variable ever become zero again? The perldoc seems to say it is never reinitialized.

      I didn't know about using 'each' with an array, as in your third alternative. That's neat.

      Obviously there's not a brilliantly compact idiom :-)

        Mr. Muskrat++ for other alternatives.

        See State Variable. A a "state" variable is a special kind of "my" variable that is not re-initialized ever again. Its value will persist for the life of the program. in the loop when it is seen again. When it is "out of scope", then next time it comes into scope it is reinitialized. Was new in Perl v5.10.

        See each for arrays. each with this behavior was new in Perl 5.12 and this was also new to me.

        No there is no "magic" short easy syntax for what you want to do in the general case, but you are using a common pattern that is easily recognizable. "Once upon a time, there was a princess...", we know what this story is going to be about! Good variable name like $first_time, etc. provides huge clues that something is special at the beginning. I wouldn't worry much about it.

        Update: My mistake. Thanks to Athanasius for catching this issue. The state variable is never re-initialized, even if it goes out of scope and then comes back into scope. The analogy in the documentation to a "my" variable caused a brain cramp when I read the spec. A state variable is called a "Persistent Private Variable".

      A nice survey of solutions, but as written to the opposite problem than the OP. You indent the first line; he wanted to indent all lines other than the first.

      But God demonstrates His own love toward us, in that while we were yet sinners, Christ died for us. Romans 5:8 (NASB)

Re: Easiest way to do something only on first iteration of loop
by GrandFather (Saint) on May 07, 2016 at 22:39 UTC

    It depends on context. Sometimes the flag approach in some guise is the cleanest solution. Often hoisting the first time code out of the loop is clearer, but may need thinking about the loop contents differently.

    A frequent pattern is where some first time initialisation is needed as in a loop that finds minimum and maximum values. If that just depends on the first element of an array you can easily do that outside the loop and then skip the first element in the loop:

    my $min = $values[0]; my $max = $values[0]; for my $value (@values[1 .. $#values]) { $min = $value if $value < $min; $max = $value if $value > $max ; }

    Where you are really handling several phases of processing it is clearer to use a loop for each phase. A first loop to skip over initial stuff from a file that isn't interesting for example, then a second loop to process the interesting bit.

    Even where you have special case code in the loop, often you don't need a flag variable. Just make use of undef handling. For example:

    my $firstMatchline; while (<DATA>) { next if !conditionMet($_); $firstMatchline //= $.; ... }

    If you have specific examples, show us and we'll do what we can to help.

    Premature optimization is the root of all job security
Re: Easiest way to do something only on first iteration of loop
by Marshall (Canon) on May 06, 2016 at 22:27 UTC
    In this case, I don't know why you just don't print ' ' x $indentation before starting the loop? The first line will "start after the indentation". I don't see the need for a flag in this particular case. Your code is fine for a "do it once". In a more complex case perhaps you can "do it", then start the loop at element[1],i.e. for (@arr[1 .. $#arr]) {} Update: BTW testing a flag is a "fast operation" and normally of no concern.
Re: Easiest way to do something only on first iteration of loop
by AnomalousMonk (Archbishop) on May 07, 2016 at 01:14 UTC

    c:\@Work\Perl\monks>perl -wMstrict -e "my $arrayref = [ 'The quick', 'brown fox jumped', 'over the lazy dog. +', ]; ;; for my $i (0 .. $#$arrayref) { printf ' ' x 6 if $i == 0; print qq{$arrayref->[$i] \n}; } " The quick brown fox jumped over the lazy dog.
    Sweeten with
        ... unless $i == 0;
    or
        ... if $i != 0;
    etc. to taste. (Doesn't require 5.10+.)


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

Re: Easiest way to do something only on first iteration of loop
by LanX (Saint) on May 07, 2016 at 23:24 UTC
    TIMTOWTDI ... IMHO you should choose the best self documenting approach.

    In this case I'd use a variable $index (or $idx ) for arrays and for other loops an auto-incremented $counter++

    The loop may run longer due to the increment operation, but

    print ' ' x $indentation if $idx == 0;

    is always readable and can also be extended for last line ( $idx == $#array ) or some modulo intervals

    Mr Muskrat already showed you how each can be used to get index and value in one go.

    Please be aware that your flag-approach can be better readable in other cases.

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

Re: Easiest way to do something only on first iteration of loop
by johngg (Canon) on May 07, 2016 at 11:23 UTC

    A do { ... } unless { ...; } variant.

    $ perl -Mstrict -Mwarnings -E ' my $raLines = [ qw{ Line1 Line2 Line3 } ]; sayLines( $raLines, q{ } ); sub sayLines { my( $raLines, $indent ) = @_; my $notFirst; do { print $indent; $notFirst ++; } unless $notFirst; say for @{ $raLines }; }' Line1 Line2 Line3 $

    Cheers,

    JohnGG

Re: Easiest way to do something only on first iteration of loop
by Anonymous Monk on May 06, 2016 at 23:07 UTC
    #!/usr/bin/perl # http://perlmonks.org/?node_id=1162396 use strict; use warnings; # display_paragraph(array_ref, indentation) # # print lines of array, indenting all but first line. # Note: use strict and use warnings are in effect, # so must declare 'my' vars! # sub display_paragraph { my $aref = shift; my $indentation; for my $line (@$aref) { print ' ' x ($indentation // 0), "$line\n"; $indentation //= shift; } } display_paragraph( [ qw( one two three four five )], 2);
Re: Easiest way to do something only on first iteration of loop
by ww (Archbishop) on May 06, 2016 at 22:34 UTC

    see perldoc -f last or use Super Search.

    Update: Didn't think this one through carefully enough. last, next, continue have their excellent uses, but using last is the wrong choice in that family for use in a sub handling the indenting and using next to exit the sub I envisioned provokes perl to add a message in the middle of the output:

    Exiting subroutine via next at D:\_Perl_\PMonks\1162396.pl line 31.

    Please do not upvote this half-baked suggestion.


    check Ln42!

Re: Easiest way to do something only on first iteration of loop
by GotToBTru (Prior) on May 09, 2016 at 11:54 UTC

    This is slightly less verbose.

    sub display_paragraph() { my $aref = shift; my $indentation = shift; my $skip_first = 0; # declare and init a flag for my $line (@$aref) { if ($skip_first++) { # false first time thru, true thereafte +r print ' ' x $indentation; } print "$line\n"; } }
    But God demonstrates His own love toward us, in that while we were yet sinners, Christ died for us. Romans 5:8 (NASB)

Re: Easiest way to do something only on first iteration of loop
by ctilmes (Vicar) on May 09, 2016 at 13:58 UTC

    Several good ways to do this have already been mentioned for Perl 5.

    I'll also note that your question was one of the many things addressed in Perl6.

    Perl 6 added an explicit phaser for first loop invocation you can use like this:

    for @arr -> $line { FIRST { print ' ' xx $indentation }; say $line; }
      I only realized now that all of the above solutions have the condition backward: the first time through the loop I don't want to print an indentation. An outer routine has already typed a "header", like a number or a bullet, to the left and the print-head is now positioned at the indentation column at the time the subroutine is called. The first line must be printed without further indentation, but any subsequent lines will need indentation.

      Interestingly, it was only when reading the Perl 6 syntax that this leaped out at me. For my purpose I'd need the NEXT phaser rather than the FIRST phaser. Thanks for the tip.