last iteration of a for loop

H

hymie!

Greeings.

So I've got a for loop. It's doing some printing, and at the end of
what it prints, it prints a separator line like

----------

Is there an easy way that I can tell my loop "don't print the separator
after the last iteration"?

I know I can say
print "----------" unless $i==9;
but I was hoping there might be something more robust or generic, or that
would work in a more complex loop such as
foreach $day (sort { daynum($a) <=> daynum($b) } keys %sched)

Alternately, maybe I can print the separator at the **top** of my
loop, except on the **first** iteration? I can probably do that
with a flag, but again, I was hoping for something more robust and
perl-ish.

--hymie! http://lactose.homelinux.net/~hymie (e-mail address removed)
-------------------------------------------------------------------------------
 
J

Janek Schleicher

Am 26.02.2014 15:30, schrieb hymie!:
So I've got a for loop. It's doing some printing, and at the end of
what it prints, it prints a separator line like

----------

Is there an easy way that I can tell my loop "don't print the separator
after the last iteration"?

I know I can say
print "----------" unless $i==9;
but I was hoping there might be something more robust or generic, or that
would work in a more complex loop such as
foreach $day (sort { daynum($a) <=> daynum($b) } keys %sched)

Alternately, maybe I can print the separator at the **top** of my
loop, except on the **first** iteration? I can probably do that
with a flag, but again, I was hoping for something more robust and
perl-ish.


a way for a
foreach my $item (@not_changing_list) {
...
} loop would be too rewrite it as a: [untested]

for (my ($item, @l) = @not_changing_list; $item = shift @l; @l > 0) {
...
print "--------\n" if @l;
# @l is empty, so false when processing last item
}


It has obv the disadvantage that the original list has first to be
copied, but looks anyway solid unless we are talking about massive data
sizes or side effects (e.g. when working with Tie::File).


Greetings,
Janek
 
J

Jürgen Exner

Greeings.

So I've got a for loop. It's doing some printing, and at the end of
what it prints, it prints a separator line like

This is a very typical scenario and it baffles a surprisingly large
number of people. There are 2 standard ways of handling it.

If it is just output you want to combine then maybe you can use the
join() function:

foreach ($elem in @list){
push @results, process_item($elem);
}
print (join($separator, @results));

Or you can process the first element separately (easier than the last
because often you don't know when you have reached the last element):

process_item($list[0]);
foreach ($elem in @list[1..@list]){
print $separator;
process_item($elem);
}

Or with HOFs you can use a standard reduce.

jue
 
J

Janek Schleicher

Am 26.02.2014 16:50, schrieb Janek Schleicher:
for (my ($item, @l) = @not_changing_list; $item = shift @l; @l > 0) {

should be of course
for (my ($item, @l) = @not_changing_list; @l > 0; $item = shift @l) {

(use C-stylish for loops so rarely)
...
print "--------\n" if @l;
# @l is empty, so false when processing last item
}


A complete different way that works more generic would be to use one of
the Iterator modules from CPAN, e.g.

use Array::Iterator;

for (my $i = Array::Iterator->new(@array); $i->has_next(); $i->next()) {
my $current = $i->current();
# .. do something with current
print "------" if $i->has_next();
}

in that way can also work with non constant lists.


Greetings,
Janek
 
R

Rainer Weikusat

So I've got a for loop. It's doing some printing, and at the end of
what it prints, it prints a separator line like

----------

Is there an easy way that I can tell my loop "don't print the separator
after the last iteration"?

I know I can say
print "----------" unless $i==9;
but I was hoping there might be something more robust or generic, or that
would work in a more complex loop such as
foreach $day (sort { daynum($a) <=> daynum($b) } keys %sched)

Alternately, maybe I can print the separator at the **top** of my
loop, except on the **first** iteration? I can probably do that
with a flag, but again, I was hoping for something more robust and
perl-ish.

AFAIK, you've hit a fundamental, conceptual limitation in the way
"structured programming loops" are defined: The notion of "some piece of
code which gets executed prior to each re-execution of the loop body"
doesn't exist. It is possible to work around this for do - while loops
with goto a la

goto doit;
do {
# print separator

doit:
# print other stuff
} while keep_on_running();

but that's not a good idea in Perl because Perl gotos are very expensive
and it doesn't work for loops which check the condition prior to
executing the loop-body for the first time.

The usual workarounds would be "print separator at the top and 1st
element before the loop" or "print separator at the bottom and first
element after the loop".
 
R

Rainer Weikusat

Janek Schleicher said:
Am 26.02.2014 16:50, schrieb Janek Schleicher:

should be of course
for (my ($item, @l) = @not_changing_list; @l > 0; $item = shift @l) {

(use C-stylish for loops so rarely)



A complete different way that works more generic would be to use one
of the Iterator modules from CPAN, e.g.

use Array::Iterator;

for (my $i = Array::Iterator->new(@array); $i->has_next(); $i->next()) {
my $current = $i->current();
# .. do something with current
print "------" if $i->has_next();
}

in that way can also work with non constant lists.

That equivalent to the print .... if $i == 9 the author wanted to get
rid of (in both cases and besides being an atrocious attempt if
imitating Java in Perl for the sake of 'eyesore' ...).
 
R

Rainer Weikusat

[...]
The notion of "some piece of
code which gets executed prior to each re-execution of the loop body"
doesn't exist.
[...]

The usual workarounds would
[...]

or "print separator at the bottom and first element after the loop".

s/first/last
 
J

Janek Schleicher

Am 26.02.2014 17:13, schrieb Rainer Weikusat:
That equivalent to the print .... if $i == 9 the author wanted to get
rid of (in both cases and besides being an atrocious attempt if
imitating Java in Perl for the sake of 'eyesore' ...).

You mean beside, not having to track the not needing loop nr and beside
having magical nrs in your code, beside it works easy when changing the
list in any way that is not too drastic, or to say it with OP's words:

"
I know I can say
print "----------" unless $i==9;
but I was hoping there might be something more robust or generic, or that
would work in a more complex loop such as
foreach $day (sort { daynum($a) <=> daynum($b) } keys %sched)
"

Both my proposed solutions would work easy (to implement, read, maintan,
adopt), robust, generic with that and even if you got a more complex
question (like an XML feed or a complex asynchronous calculation), just
take another CPAN module for Iterators, usually all you have to give is
a sub of how to get the next elem and here indeed wheter the next elem
even exists. But you certainly can get rid of having to hard code it in
the loop.

I don't know what you expected,
I mean at some point we have to say to Perl or control flow, that a part
of the loop only has to be executed if it is not the last round. I think
a if @l or a if @i->has_next is close to be shorter than any
possible other syntactic sugar or any hack.


I mean, we can even make something like:
my ($first, @inbetween, $last) = @l;
HANDLE_FIRST: {
...
}
HANDLE_INBETWEEN: for ... {
...
}
HANDLE_LAST: {
...
}
what is pretty much what you proposed.

Pretty much this is what OP asked for (and maybe a compiler might
optimize us too),
all I give him is a kind of 2 shortcuts for that, that still ain't to be
hacks, still are maintainable and readable and where we don't probably
copy 3x relevant code.

Of course, that neither can handle all kind of loops (just if it could,
Perl wouldn't be Turing complete), nor it is a quicker/shorter than to
hardcode list abbrubt condition into the loop.


Make a better suggestion, (beside goto xor make 1st and last round
seperate special cases)
Greetings,
Janek
 
D

Dr.Ruud

Alternately, maybe I can print the separator at the **top** of my
loop, except on the **first** iteration? I can probably do that
with a flag, but again, I was hoping for something more robust and
perl-ish.

Yes, rather go for that.

perl -wE'
sub show {
my $sep = ("- " x 6)."-";
my $show_sep;

for my $i ( 1 .. 4 ) {
$show_sep and say $sep or $show_sep = 1;
say $i;
}
}
show;
show;
'
 
T

Tim McDaniel

Or you can process the first element separately (easier than the last
because often you don't know when you have reached the last element):

process_item($list[0]);
foreach ($elem in @list[1..@list]){
print $separator;
process_item($elem);
}

As an aside, this is an instance of "loop unrolling".
Or with HOFs you can use a standard reduce.

"HOFs"? Googling just shows that there's a town named Perl in
Saarland, where German is prevalent, and so there are lots of places
called .* Hof (.* House).
 
R

Rainer Weikusat

Janek Schleicher said:
Am 26.02.2014 17:13, schrieb Rainer Weikusat:

You mean beside, not having to track the not needing loop nr and
beside having magical nrs in your code,

I 'mean' that it is special-casing the last iteration of the loop in
exactly the same way as the original counting loop, just adapted to a
different set of 'iteration mechanics' you happen to like better than
ordinary Perl-foreach loops. For the 'traverse a list case', this can also
be written as

my @temp = produce_list_of_elements();
for (0 .. $#temp) {
# do something with $temp[$_]
print_sep() unless $_ == $#temp;
}

No need to count anything explicitly, no 'magic constants' and no for
(;;;) loop expressing something semantically equivalent in a more
complicated way. But since that's still what the OP didn't want to do
(and I'm convinced he is that knowledgable himself), I didn't post this.

[...]
I mean, we can even make something like:
my ($first, @inbetween, $last) = @l;
HANDLE_FIRST: {
...
}
HANDLE_INBETWEEN: for ... {
...
}
HANDLE_LAST: {
...
}
what is pretty much what you proposed.

I didn't propose anything, I just pointed out that a ready-made solution
doesn't exist and mentioned two other workarounds.
 
X

Xho Jingleheimerschmidt

Greeings.

So I've got a for loop. It's doing some printing, and at the end of
what it prints, it prints a separator line like

----------

Is there an easy way that I can tell my loop "don't print the separator
after the last iteration"?

I know I can say
print "----------" unless $i==9;
but I was hoping there might be something more robust or generic, or that
would work in a more complex loop such as
foreach $day (sort { daynum($a) <=> daynum($b) } keys %sched)

Alternately, maybe I can print the separator at the **top** of my
loop, except on the **first** iteration? I can probably do that
with a flag, but again, I was hoping for something more robust and
perl-ish.

I don't know of a clean solution, but have often wanted one. An "again"
block similar to the "continue" block, which only gets executed once it
is decided the loop actually will get executed again, would be nice.
(Whether it gets executed on "next" or not is an open question, I would
think not.)

I usually just use the construct
join "whatever", map {} ...

But that is certainly inconvenient when that data doesn't fit in memory
nicely, or should stream smoothly.

Xho
 
J

John Black

I don't know of a clean solution, but have often wanted one. An "again"
block similar to the "continue" block, which only gets executed once it
is decided the loop actually will get executed again, would be nice.

Isn't that what "redo" does?

John Black
 
X

Xho Jingleheimerschmidt

Isn't that what "redo" does?

Not that I know of. The redo statement repeats the loop without
re-evaluating whether it ought to be repeated, and there is no redo block.

perl -le 'for (1..10) {print $_;} continue {print "-----"}'

a redo statement after the 'print $_;' sends this into an infinite loop,
and a continue statement does not alter the behavior from original.

Xho
 
T

Tim McDaniel

I don't know of a clean solution, but have often wanted one. An
"again" block similar to the "continue" block, which only gets
executed once it is decided the loop actually will get executed
again, would be nice.

It would be possible for a loop like
for VAR (LIST)
because it appears that LIST is evaluated once when the loop starts,
so it knows the list of values to be iterated over.

In general (for (EXPR1; EXPR2; EXPR3) or while (EXPR)), I don't see
how the loop structure could know whether it's on the last iteration
or not, because there might be global side effects or dependencies on
the loop iteration and/or termination.
 
T

Ted Zlatanov

On 26 Feb 2014 14:30:26 GMT (e-mail address removed) (hymie!) wrote:

h> So I've got a for loop. It's doing some printing, and at the end of
h> what it prints, it prints a separator line like

h> ----------

h> Is there an easy way that I can tell my loop "don't print the separator
h> after the last iteration"?

Easiest way IMO, and a useful idiom regardless:

#+begin_src perl

#!/usr/bin/perl

use warnings;
use strict;

my @list = sort keys %ENV;

while (my $key = pop @list)
{
print "$key\n";
print "not last\n" if scalar @list;
print "last\n" unless scalar @list;
}

#+end_src
 
R

Rainer Weikusat

John Black said:
Isn't that what "redo" does?

redo re-executes the loop body without evaluating the condition again.
That's a loop control 'verb' completely unrelated to the sought-after
feature. Perl has a continue-block which is executed after the loop body
and before the condition is evaluated. What would be needed here would
be a block which is executed after the loop body and after evaluating
the condition resulted in the descision to execute the loop body for
another time. This could be simulated in Perl like this:

{
my $repeated;
while (condition()) {
if ($repeated) {
# special block goes here
}

# ordinary loop body
$repeated = 1;
}
}

but this is really a bad implementation because it keeps assigning to
the indiciator variable and keeps retesting its value despite it'll
never change after the first iteration. A good implementation (in this
sense) would be

if (condition()) {
goto body;
do {
# special block

body:
# loop body
} while (condition());
}

but this is really a transformation which should be performed
automatically by a compiler and certainly shouldn't be implemented in
Perl. Since this is a common construct, a "high-level" syntax for
expressing it would be helpful, ie one which neither relies on explicit
gotos to indicate the desired control flow nor on mostly useless code.
 
J

Justin C

print "not last\n" if scalar @list;
print "last\n" unless scalar @list;

Maybe it's subtle, but I don't see a difference between the
above and the below:

print "not last\n" if @list;
print "last\n" unless @list;


Justin.
 
T

Ted Zlatanov

JC> Maybe it's subtle, but I don't see a difference between the
JC> above and the below:

JC> print "not last\n" if @list;
JC> print "last\n" unless @list;

The scalar context is applied automatically, so there is no difference
here.

Having been bitten by this in the past (non-scalar context applied when
I wanted scalar), I prefer to always be explicit. Similarly, I use
"shift @_" and "shift @ARGV" explicitly in most situations. Both of
these are probably too cautious for typical Perl usage today.

Ted
 
J

Janek Schleicher

Am 27.02.2014 18:31, schrieb Ted Zlatanov:
JC> Maybe it's subtle, but I don't see a difference between the
JC> above and the below:

JC> print "not last\n" if @list;
JC> print "last\n" unless @list;

The scalar context is applied automatically, so there is no difference
here.

Having been bitten by this in the past (non-scalar context applied when
I wanted scalar), I prefer to always be explicit. Similarly, I use
"shift @_" and "shift @ARGV" explicitly in most situations. Both of
these are probably too cautious for typical Perl usage today.

Of course, that's both personal style.

I agree to you, that you try to avoid unclear spots,
but there aren't anyone in boolean context.

For me, if there is a scalar ... line in source code, I always think
a) that is important
b) it can't be solved otherwise
c) something is wrong with our design, as we don't use the objects
naturally.


Greetings,
Janek
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top