last iteration of a for loop

Discussion in 'Perl Misc' started by hymie!, Feb 26, 2014.

  1. hymie!

    hymie! Guest

    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
    -------------------------------------------------------------------------------
    hymie!, Feb 26, 2014
    #1
    1. Advertising

  2. 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
    Janek Schleicher, Feb 26, 2014
    #2
    1. Advertising

  3. (hymie!) wrote:
    >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"?


    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ürgen Exner, Feb 26, 2014
    #3
  4. 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
    Janek Schleicher, Feb 26, 2014
    #4
  5. (hymie!) writes:
    >
    > 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".
    Rainer Weikusat, Feb 26, 2014
    #5
  6. Janek Schleicher <> writes:
    > 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.


    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' ...).
    Rainer Weikusat, Feb 26, 2014
    #6
  7. Rainer Weikusat <> writes:

    [...]

    > 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
    Rainer Weikusat, Feb 26, 2014
    #7
  8. Am 26.02.2014 17:13, schrieb Rainer Weikusat:
    >> 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' ...).


    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
    Janek Schleicher, Feb 26, 2014
    #8
  9. hymie!

    Dr.Ruud Guest

    On 2014-02-26 15:30, hymie! wrote:

    > 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;
    '

    --
    Ruud
    Dr.Ruud, Feb 26, 2014
    #9
  10. hymie!

    Tim McDaniel Guest

    In article <>,
    J_rgen Exner <> wrote:
    >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).

    --
    Tim McDaniel,
    Tim McDaniel, Feb 26, 2014
    #10
  11. Janek Schleicher <> writes:
    > Am 26.02.2014 17:13, schrieb Rainer Weikusat:
    >>> 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' ...).

    >
    > 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.
    Rainer Weikusat, Feb 26, 2014
    #11
  12. On 02/26/14 06:30, hymie! wrote:
    > 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
    Xho Jingleheimerschmidt, Feb 27, 2014
    #12
  13. hymie!

    John Black Guest

    In article <lema06$tjn$>, says...
    > 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
    John Black, Feb 27, 2014
    #13
  14. On 02/26/14 19:21, John Black wrote:
    > In article <lema06$tjn$>, says...
    >> 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?
    >


    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
    Xho Jingleheimerschmidt, Feb 27, 2014
    #14
  15. hymie!

    Tim McDaniel Guest

    In article <lema06$tjn$>,
    Xho Jingleheimerschmidt <> wrote:
    >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.

    --
    Tim McDaniel,
    Tim McDaniel, Feb 27, 2014
    #15
  16. hymie!

    Ted Zlatanov Guest

    On 26 Feb 2014 14:30:26 GMT (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
    Ted Zlatanov, Feb 27, 2014
    #16
  17. John Black <> writes:
    > In article <lema06$tjn$>, says...
    >> 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?


    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.
    Rainer Weikusat, Feb 27, 2014
    #17
  18. hymie!

    Justin C Guest

    On 2014-02-27, Ted Zlatanov <> wrote:
    > 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.

    --
    Justin C, by the sea.
    Justin C, Feb 27, 2014
    #18
  19. hymie!

    Ted Zlatanov Guest

    On Thu, 27 Feb 2014 15:47:02 +0000 Justin C <> wrote:

    JC> On 2014-02-27, Ted Zlatanov <> wrote:
    >> print "not last\n" if scalar @list;
    >> print "last\n" unless scalar @list;


    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
    Ted Zlatanov, Feb 27, 2014
    #19
  20. Am 27.02.2014 18:31, schrieb Ted Zlatanov:
    > On Thu, 27 Feb 2014 15:47:02 +0000 Justin C <> wrote:
    >
    > JC> On 2014-02-27, Ted Zlatanov <> wrote:
    >>> print "not last\n" if scalar @list;
    >>> print "last\n" unless scalar @list;

    >
    > 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
    Janek Schleicher, Feb 27, 2014
    #20
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Florian Lindner

    Last iteration?

    Florian Lindner, Oct 12, 2007, in forum: Python
    Replies:
    24
    Views:
    633
    Gabriel Genellina
    Oct 20, 2007
  2. Rudi
    Replies:
    5
    Views:
    4,963
  3. Mike

    Last iteration condition

    Mike, Apr 3, 2007, in forum: Ruby
    Replies:
    12
    Views:
    182
    Robert Klemme
    Apr 3, 2007
  4. Nene
    Replies:
    6
    Views:
    340
    John W. Krahn
    Dec 13, 2008
  5. Isaac Won
    Replies:
    9
    Views:
    350
    Ulrich Eckhardt
    Mar 4, 2013
Loading...

Share This Page