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 ?
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. | [reply] [d/l] [select] |
Re: Perl's feature to determine, in current point of loop, that this is the last one?
by jwkrahn (Monsignor) on Jan 23, 2022 at 03:52 UTC
|
$ 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
| [reply] [d/l] |
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 | [reply] [d/l] [select] |
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
|
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.
| [reply] [d/l] [select] |
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.
| [reply] [d/l] [select] |
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
|
| [reply] |
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.
| [reply] |
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);
}
| [reply] [d/l] |
|
while (@a) {
shift(@a);
if ( is_last_pass() ) {
push @a, "foo";
}
}
But, you can "peek ahead" sometimes. | [reply] [d/l] |
|
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
| [reply] [d/l] [select] |
Re: Perl's feature to determine, in current point of loop, that this is the last one?
by cavac (Vicar) 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";'
| [reply] [d/l] [select] |
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
|
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.
| [reply] [d/l] |
|
| [reply] |
|
| [reply] |
|
|
|
|
|
|
|
|