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

I have a loop where I'd like have something happening between each loop interation, but not after the last iteration. This can be done with some kind of sempahor variable, e.g.:
$count = 2; foreach (0..2) {} continue {print $_,"\n" if $count--}
Where $count is acting as a sempaphore.

I'm wondering if there is some way to do this without the semaphore. For example, is there some CPAN module that implements a thing like continue that doesn't fire off after the last loop iteration? Or maybe some algorithmic trick I haven't though of. This needs to work with a foreach where I'm not keeping any explicit loop counter (unlike the baby example above).

--DrWhy

"If God had meant for us to think for ourselves he would have given us brains. Oh, wait..."

Replies are listed 'Best First'.
Re: I need a different continue
by diotalevi (Canon) on Dec 09, 2004 at 18:18 UTC

    Your foreach won't know how many times to loop until it has already started execution. What you could do is keep a queue of todos and every time you start the loop block, do anything needing to be finished. The last time through your loop the todos generated during the loop won't be executed and will go away.

    { my @todos; foreach my $elt ( ... ) { $_->() for @todos; # Correction for Roy Johnson @todos = (); ... push @todos, sub { print "$elt\n" }; } }
      I like this, but you're running all the todos each time through the loop. That is, on iteration N, you'll run N-1 todos. You need to do more like
      { my $todo; foreach my $elt (0..2 ) { $todo->() if $todo; $todo = sub { print "$elt\n" }; } }

      Caution: Contents may have been coded under pressure.
Re: I need a different continue
by Joost (Canon) on Dec 09, 2004 at 18:36 UTC
    In general, you can't when using a foreach() loop. In specific instances, you can use join() (when joining a string), shift elements off the array and check its length, or or use an iterator object to abstract it all away:
    while (my $value = $it->next()) { # stuff if ($it->has_next()) { # for all but the last value } }
    update: equivalent with shift

    while (@array) { my $value = shift @array; # stuff if (@array) { # for all but the last value } }
    update2: fixed logic error: s/unless/if/; - sorry about that.
Re: I need a different continue
by Roy Johnson (Monsignor) on Dec 09, 2004 at 18:50 UTC
    Another possibility is to use a C-style for loop:
    { my (@arr, $elt); for (@arr=(0..2); ($elt, @arr) = @arr; print "Continue $elt\n" if @ +arr) { print "Iteration $elt\n"; } }

    Caution: Contents may have been coded under pressure.
Re: I need a different continue
by hmerrill (Friar) on Dec 09, 2004 at 18:19 UTC
    What is your loop based on? Is it like the above 0..2, or is it data driven? Can you post a part of the loop here to show the structure?

    I don't know of another algorithm or module that does what you're looking for, but if you describe the loop in more detail, I (or someone else) might know of an approach you haven't thought of yet.

      Here's a simplified version of what I have now.
      lock_db_tables(); # lock tables and/or start a transaction # in a database independent way # some preparation that requires a database lock my $firsttime = 1; foreach my $id (@list_o_object_ids) { lock_db_tables() unless $firsttime; $firsttime = 0; my $obj = new Thingy($id); # gets stuff from db # make changes, save back to db. unlock_tables(); }
      The pre-loop code and the first loop iteration need to be in the same transaction. Subsequent loop iterations need to each be their own transaction.

      --DrWhy

      "If God had meant for us to think for ourselves he would have given us brains. Oh, wait..."

        I guess it's the unlock_tables() that you want to run on all except the last iteration, right? you can move it to the beginning of the loop and skip it in the first iteration:

        my $firsttime = 1; foreach my $id (@list_o_object_ids) { unless ($firsttime) { unlock_tables(); lock_db_tables(); } else { $firsttime = 0; } my $obj = new Thingy($id); # gets stuff from db # make changes, save back to db. }

        Jenda
        We'd like to help you learn to help yourself
        Look around you, all you see are sympathetic eyes
        Stroll around the grounds until you feel at home
           -- P. Simon in Mrs. Robinson

Re: I need a different continue
by gaal (Parson) on Dec 09, 2004 at 18:27 UTC
    If you don't need foreach's aliasing effect, you can do something like:

    while (1) { last unless (EXPR); # code } continue { # code }
Re: I need a different continue
by Jenda (Abbot) on Dec 10, 2004 at 17:37 UTC

    As shown in my node burried down in the thread, if you need to do something at the end of each iteration except the last one do it at the beginning of all iterations except the first one. Easier to do and the result is the same.

    Well except if you use the continue{} as well, then the order of execution would not be exactly the same.

    Jenda
    We'd like to help you learn to help yourself
    Look around you, all you see are sympathetic eyes
    Stroll around the grounds until you feel at home
       -- P. Simon in Mrs. Robinson