Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Perl's feature to determine, in current point of loop, that this is the last one?

by Anonymous Monk
on Jan 23, 2022 at 00:32 UTC ( [id://11140729]=perlquestion: print w/replies, xml ) Need Help??

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

Any knowledge on Perl to determine, when being in current point of loop, that this is the last of the loop there will be no next iteration ?

meant is there some Perl's simple internal variable or feature, not resort to some added line code/variable must be done by coder ?
  • Comment on Perl's feature to determine, in current point of loop, that this is the last one?

Replies are listed 'Best First'.
Re: Perl's feature to determine, in current point of loop, that this is the last one? (updated)
by haukex (Archbishop) on Jan 23, 2022 at 08:32 UTC
    meant is there some Perl's simple internal variable or feature, not resort to some added line code/variable must be done by coder ?

    Having wished for this myself, I can say that the answer to the general question is unfortunately: No, with the exception of eof when reading from files, as mentioned by kcott.

    TIMTOWTDI, though. You haven't shown any example code so it's really hard to say which variation below (if any) might be most appropriate or easiest in your case.

    use warnings; use strict; my @array = qw/ Foo Bar Quz /; # C-style loop, not very perlish (but *sometimes* useful) for ( my $i = 0; $i < @array; $i++ ) { print $array[$i], $i==$#array ? ".\n" : ", "; } # the perlish variant of the above for my $i (0..$#array) { print $array[$i], $i==$#array ? ".\n" : ", "; } use 5.012; # so keys/values/each work on arrays while ( my ($i, $v) = each @array ) { print $v, $i==$#array ? ".\n" : ", "; } # as suggested by jwkrahn my $cnt = @array; for my $v (@array) { print $v, --$cnt ? ", " : ".\n"; } # or sometimes it's as simple as "join" print join(", ", @array), ".\n"; my @array_copy = @array; # because it's destructive! while ( my $v = shift @array_copy ) { print $v, @array_copy ? ", " : ".\n"; }

    And lastly, while it might seem overkill, the concept of iterators can be extremely useful in some cases. The book Higher-Order Perl is a great read on this and other topics. Here, I've extended the concept with a "peekable" iterator class. One can even overload the <> operator as I showed here (note one does need Perl v5.18 or better for the overloaded <> to work in list context). Update: In the following code, I've replaced my own classes with Iterator::Simple and Iterator::Simple::Lookahead. /Update

    # demo of a basic iterator use Iterator::Simple 'iter'; my $it = iter(\@array); while (defined( my $v = $it->() )) { print $v, " "; } print "\n"; # demo the peekable iterator use Iterator::Simple::Lookahead; my $itp = Iterator::Simple::Lookahead->new( iter(\@array) ); while (defined( my $v = $itp->() )) { print $v, defined $itp->peek ? ", " : ".\n"; }

    Bonus: For some experimental stuff, see FIRST and LAST in Perl6::Controls.

    Also minor edits to wording.

Re: Perl's feature to determine, in current point of loop, that this is the last one?
by jwkrahn (Abbot) on Jan 23, 2022 at 03:52 UTC

    Do you mean something like this:

    $ perl -le' my @array = "A" .. "H"; my $count = @array; for my $letter ( @array ) { if ( --$count ) { print "Letter: $letter"; } else { print "Last letter: $letter"; } } ' Letter: A Letter: B Letter: C Letter: D Letter: E Letter: F Letter: G Last letter: H
Re: Perl's feature to determine, in current point of loop, that this is the last one?
by syphilis (Archbishop) on Jan 23, 2022 at 04:51 UTC
    ...that this is the last of the loop there will be no next iteration ?

    As shown in jwkrahn's example, you have to code it in yourself. (And this is usually quite possible.)

    But perl itself will not know that it's in the last loop until that last loop has terminated and the looping condition is re-evaluated (and fails).
    By that time the moment has passed, and all perl could tell you is "Oh ... that last loop I just did was the final loop".

    If you're looking for a way to bail out of a loop before the loop condition has failed, you can always use last:
    perl -le 'for(1..10){print $_; last if $_ == 5}' 1 2 3 4 5
    Cheers,
    Rob
Re: Perl's feature to determine, in current point of loop, that this is the last one?
by ikegami (Patriarch) on Jan 23, 2022 at 08:44 UTC

    Replace

    for my $x ( ... ) { ... if ( ??? ) { ... } ... }
    with
    my @a = ...; for my $i ( 0 .. $#a ) { my $x = $a[$i]; ... if ( $i == $#a ) { ... } ... }

    This will work too:

    my @a = ...; for my $x (@a) { ... if ( \$x == \$a[-1] ) { ... } ... }

    Warning: As LanX pointed out, the last snippet fails if two elements of the array are aliases of the same scalar.


    Update: Added warning.

Re: Perl's feature to determine, in current point of loop, that this is the last one?
by davido (Cardinal) on Jan 23, 2022 at 19:06 UTC

    There's no general solution for all types of loops and all data the loops may be iterating over. Could you explain what your data source is, and whether you're using a for/foreach loop, a while loop, or something else? I assume you're using a loop that looks like:

    foreach my $element (@array) {...

    But that could be a mistaken assumption. Also, how different does that last element need to be treated? What populated the array, and what will you be doing with it afterwards?

    This is probably a terrible idea but you could bless a reference to the last element and detect that blessing:

    my @array = qw(a b c d e f g); { local $array[-1] = $array[-1]; bless \$array[-1], 'last'; foreach my $element (@array) { if (ref(\$element) eq 'last') { print "This is the last element: "; } print "$element\n"; } }

    The straightforward approach of just counting elements is less opaque. And if you're going to do that, a C style loop is probably the logical conclusion. However, if we knew more about where the data is coming from and what is really needed we may be able to provide a more on-target solution.


    Dave

Re: Perl's feature to determine, in current point of loop, that this is the last one?
by kcott (Archbishop) on Jan 23, 2022 at 02:26 UTC
Re: Perl's feature to determine, in current point of loop, that this is the last one?
by Fletch (Bishop) on Jan 23, 2022 at 07:47 UTC

    Depending on what exactly you're trying to do perhaps Template::Iterator from Template Toolkit might be of inspiration (see also the TT docs).

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

Re: Perl's feature to determine, in current point of loop, that this is the last one? (updated: while unpredictable)
by LanX (Saint) on Jan 23, 2022 at 11:27 UTC
    That depends on the loop (for,while,map,...) and the iterated thing.

    Could you be more specific, or at least point to another language which can do this?

    update

    Here a counter example to explain one difficulty:

    Consider a while loop iterating over human input, how should the loop know in advance that the user will stop the input in the next iteration?

    while ( my ($in) = human_input() ) { do_something($in); }

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      It can't be solved generically for while loops.

      while (@a) { shift(@a); if ( is_last_pass() ) { push @a, "foo"; } }

      But, you can "peek ahead" sometimes.

      So lets suppose it's "only" needed for the case foreach(@array) which isn't an iterator but dealing with a flattened list, right?

      I don't think so, @array could be tied to an iterator via Tie::Array and IIRC there are already modules on CPAN exploiting this "backdoor".

      FWIW it also offers some syntactic sugar to implement your desired feature for a static @array, by designing a sub which returns a tied array-ref wrapping the static one:

      for my $var ( @{ guard_last(@array,my $last) } ) { say "IS LAST" if $last; say $var; }
      edit

      And I'm pretty sure this won't work with Perls older than ~5.10

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

Re: Perl's feature to determine, in current point of loop, that this is the last one?
by cavac (Parson) on Jan 26, 2022 at 13:09 UTC

    There are so many ways to construct a loop. I'm not talking about syntax, i'm talking about the logic behind it and what the loop is trying to achieve. And even in a for() loop you might have some conditions that terminate the loop early (or restart the iterator).

    For example, imagine a simple software that wants to run 100 loops of sending some data over the network. If the connection breaks, you'll have to restart from the beginning, or in some cases abort altogether. Let's write a small simulation:

    #!/usr/bin/env perl use strict; use warnings; my $restartcount = 0; my $done = 1; for(my $i = 0; $i < 100; $i ++) { # Do your complex stuff here print "$i\n"; # Simulate some kind of random error # that requires a restart of the loop if(int(rand(100)+1) % 23 == 0) { print "Yada-Yada error, restarting loop\n"; $i = 0; $restartcount++; } # Simulate a condition that requires an abort if(int(rand(1000)+1) == 42) { print "Fatal error, aborting after $restartcount tries\n"; $done = 0; last; } } if($done) { print "Loop done\n"; print "Had to restart $restartcount times\n"; }

    There is only a very small chance that the Perl interpreter is even theoretically capable of predicting when it is the last loop... until it exits the loop in one of three completely different ways. Depending on how borked your pseudo-random number generator is, it's theoretically possible that the script will never exit the loop. It's a classic halting problem. Alan Turing himself stated that it's impossible to predict the outcome.

    perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'
Re: Perl's feature to determine, in current point of loop, that this is the last one?
by talexb (Chancellor) on Jan 23, 2022 at 20:45 UTC

    You could always watch for the last value in the list and test for that ..

    my @values = qw/red green brown blue/; foreach my $v ( @values ) { if ( $v eq $values[-1] ) { # Last value } else { # All other values } }
    As you've already heard, there's nothing built in to Perl that handles that.

    Alex / talexb / Toronto

    Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

      > You could always watch for the last value in the list and test for that ..

      this only works if the last value is unique

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        Absolutely right -- I knew that my approach had flaws, but the initial question was so vague I figured I'd throw my solution into the mix. The OP never explained why this feature was necessary .. it sounds more like a feature looking for a use case to me.

        Alex / talexb / Toronto

        Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

Log In?
Username:
Password:

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

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

    No recent polls found