Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Automatic Loop Counter not in perl

by b4swine (Pilgrim)
on Aug 21, 2007 at 02:18 UTC ( [id://633981]=perlmeditation: print w/replies, xml ) Need Help??

Considering what else is provided by perl, (for example the $. variable), I have always felt the lack of a special variable (call it $LOOPCOUNTER), which starts from 0, and counts the number of times the innermost while, for, etc... has run. There would only be one such variable, so you could only use it in the innermost loop. Any loop would reset it to zero when starting, and increment it at each iteration.

With which you could write:

print "$LOOPCOUNTER $_\n" for @x;
instead of the painful,
print "$_ $x[$_]\n" for 0..@x-1;

In the example above, there doesn't seem to be much of an advantage, but in other situations, it would not only be handy, but much more legible, for example

print "$LOOPCOUNTER $_\n" for keys %$LoH->[$count];
is a lot nicer than
print "$_ keys %LoH->[$count]{$_}\n" for 0..(keys %LoH->[$count])-1;
or
my $count = 0; print $count++ . " $_\n" for keys %LoH->[$count];

Obviously there is an efficiency hit of having an increment for every loop iteration whether you want it or not. On the other hand, very often the counter is desirable, and having a dedicated counter might be more efficient than a lexical my variable that is often used in its place.

This is my first meditation, so please be gentle, in case this is a stupid idea.

Replies are listed 'Best First'.
Re: Automatic Loop Counter not in perl
by Joost (Canon) on Aug 21, 2007 at 10:50 UTC
    One thing I don't like about your proposed variable is that as far as I can see, you'd have to do additional assignment of the $LOOPCOUNTER variable if you want to nest loops.

    I prefer the way more "functional/OO" languages like javascript and ruby implement foreach loops using callbacks with explicit parameters. Something that's easy to implement in perl for fullblown objects:

    $list->foreach( sub { my ($element,$index) = @_; print "$index $element\n"; });
    If perl's prototypes would work for methods, you could build something very close to ruby's
    list.each_with_index { |element,index| print "#{index} #{element}\n" };

    For completeness, here's the javascript version:

    array.forEach(function(element,index) { document.write(index+" "+element+"\n"); // depending on the host });
    Note that ruby & javascript's versions also have the advantage of being easily overridable for any object (including "standard arrays", since both allow methods to be attached to objects instead of only to "object types" / classes - and, of course, in both languages arrays are always "real" objects).

    updated: fixed typo, slight cleanup of wording.

Re: Automatic Loop Counter not in perl
by roboticus (Chancellor) on Aug 21, 2007 at 11:33 UTC
    b4swine:

    Amusing idea. You could make perl skip the overhead of the loopcount if the compiler doesn't see anything using the variable.

    If you wanted the ability to access the loopcount of other contexts, you might have the ability to treat it as an array, where $loopcount[-1] is the loopcount of the context containing this one. Getting even further out, you could also let loopcount be a hash so you could access the loopcount of a named loop with $loopcount{OUTER} or some such.

    OBTW, ++ on your moniker!

    ...roboticus

      ++b4swine for a nice meditation, and ++roboticus for further expanding on the idea.

      Many have been the times I've gone from having a simple foreach loop to a for loop, just because I realized that the indices would be required.

      For example, let's say I started with a straightforward:

      #!/usr/bin/perl -w use strict; use warnings; my @array = qw( a b c d e f g h ); # Code sample 1 -- for simplicity, just display each array item: foreach (@array) { printf "%s\n", $_; }

      Then I realize that I need the index each time through the loop, for whatever reason.  Again, for simplicity's sake, let's assume the index is only needed for display purposes.  Even so, it requires changing from a foreach loop to a for loop, and adding a line to set some variable to the next item in the list each time:

      # Code sample 2 -- Come to think of it, I want the indices too ... for (my $i = 0; $i < @array; $i++) { my $item = $array[$i]; printf "%3d. $item\n", $i + 1; }

      But wouldn't it be nice if there were a default variable set to the index?  Then I could make minimal changes to the original foreach loop:

      # Code sample 3 -- minor changes from the original "foreach" # loop (Code sample 1) foreach (@array) { printf "%3d. %s\n", $loopcount + 1, $_; }

      I like roboticus' idea of having an array or hash to hold multiple levels of the variable.  Ideally, there would be both, a scalar variable which holds the index of the current block, and an array or hash holding the indices from all loops.

      The only thing I would suggest to do differently is find a special variable to replace $loopcount.  An obvious choice might be $#, which is already deprecated in its current use --   from perlvar:

      $# The output format for printed numbers. This variable is a half-hearted attempt to emulate awk's OFMT variable. There are times, however, when awk and Perl have differing notions of what counts as numeric. The initial value is "%.ng", where n is the value of the macro DBL_DIG from your system’s float.h. This is different from awk's default OFMT setting of "%.6g", so you need to set $# explicitly to get awk's value. (Mnemonic: # is the number sign.) Use of $# is deprecated.

      s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
        I'd do it like this
        my @array = qw{zero one two}; for my $i (0..$#array){ print qq{$i\t$array[$i]\n}; }

        So, if

        $#

        is depreciated. Is there something equivalent to use?

        Have a nice day :-)
Re: Automatic Loop Counter not in perl
by jdporter (Chancellor) on Aug 21, 2007 at 11:02 UTC
    you could only use it in the innermost loop. Any loop would reset it to zero when starting

    Even better would be for it to be automatically localized, just as $_ is in foreach loops.

    A word spoken in Mind will reach its own level, in the objective world, by its own weight
      Why not just make it a stack? We can easily index the last element(s) by using negative offsets, so:
      for my $outer ( 0..1 ) { for my $inner ( 5..6 ) { print $LOOPCOUNT[-1], "+", $LOOPCOUNT[-2], "\n"; } }


      holli, /regexed monk/
        Why not just make it a stack?

        What do you think local is doing?

        One of the cool things about localizable variables is that when you take a reference to one, you're getting a reference to that instance of it in its internal stack. So, if you need to reach back to values in outer scopes, you can take references to the variable at those levels.

        use strict; use warnings; our $LC = 'o'; my $lc0 = \$LC; for ( local $LC = 0; $LC < 3; $LC++ ) { my $lc1 = \$LC; for ( local $LC = 0; $LC < 3; $LC++ ) { print "lc0=$$lc0, lc1=$$lc1, LC=$LC, \n"; } }
        A word spoken in Mind will reach its own level, in the objective world, by its own weight
Re: Automatic Loop Counter not in perl
by duff (Parson) on Aug 21, 2007 at 14:19 UTC

    I like Perl 6's mechanism:

    for @array.kv -> $i, $v { print "Item at index $i has value $v\n"; }

    If only there were a way to incorporate it into Perl 5. Maybe I should add that to the wish list for perl 5.12

      Yes, Perl 6's solution is rather generic.

      A loop counter can easily be done like this:

      for @array Z (0..*) -> $item, $count { say "$item occured at position $count"; }

      An array is (lazily) zipped with a (lazy) infinite array (1, 2, 3 Z 'a', 'b', 'c' evaluates to (1, 'a'), (2, 'b', (3, 'c'), in list context it is flattened (1, 'a', 2, 'b', 3, 'c') and the two loop variables eat up two elements per iteration.

      Note that

      • It is not much to type
      • the counter is scoped to the loop block
      • you can easily and intuitivly set the lower boundary of the counter
      • you can choose arbitrary for the counter
      • You only do that when you really need a counter, so no runtime penalties for loops without counter
      • You can omit the ->$item, $counter part and use self declaring parameters with the ^ twigil to save typing

      Hooray for Perl 6!

      Why dont you implement an iterator similar to Python's enumerate() function?


      Carter's compass: I know I'm on the right track when by deleting something, I'm adding functionality
Re: Automatic Loop Counter not in perl
by jhourcle (Prior) on Aug 21, 2007 at 15:16 UTC
    I have always felt the lack of a special variable (call it $LOOPCOUNTER), which starts from 0, and counts the number of times the innermost while, for, etc... has run.

    By your definition 'the number of times the innermost ... has run', I'd expect much different behaviour than what you're describing:

    There would only be one such variable, so you could only use it in the innermost loop. Any loop would reset it to zero when starting, and increment it at each iteration.

    You see, you specifically call out the 'innermost' loop, but I frequently have nested loops, and if I'm interested in the 'number of times the innermost (loop) has been called', I'm interested in the TOTAL number of times it's been called:

    my $count = 0; foreach my $arrayRef ( @twoDimArray ) { foreach my $item ( @$arrayRef ) { $count++; } }

    I'd also be concerned with the 'you could only use it in the innermost loop'. What happens if I use it, in a rather long loop, and someone comes in and adds a line that uses 'foreach' somewhere within the loop? Would it break my code? If so, it's not worth taking the risk of using it.

    How would a subroutine that contains a loop affect the restriction on only innermost loops?

    ...

    Personally, if I were to ever use something like this, I would want :

    • It to be localized, where it always works for the given loop that I'm in.
    • There two be two variables, one for the current number of times through the loop, and one for the total number of times through that loop.
    • There be consistent behaviour in how next and redo affect the counter, as well as what the value is when in a continue block.
    • There be consideration given to what the affect is when combining this variable with goto
    • There be consistent behaviour when using this variable with blocks of code that might be evaluated multiple times. (eg, could this be used to tell how many times you've entered a given function? Iterations within map or grep?)
Re: Automatic Loop Counter not in perl
by Roy Johnson (Monsignor) on Aug 21, 2007 at 17:21 UTC
    You've got some illegal syntax in %LoH->[$count]. But presuming I know what you meant, here's a nestable, localized-loop-counter-providing loop function that handles most of your wants, without* being too awkward:
    use strict; use warnings; sub counted_for (&@) { local our $LOOPCOUNT=-1; my $sub = shift; for (@_) { ++$LOOPCOUNT; $sub->(); } } # Define a modestly complicated structure to loop through my $LoH; my $count = 3; @{$LoH->[$count]}{'a'..'e'} = (); my @y = ('X'..'Z'); counted_for { print "$LOOPCOUNT: $_\n"; counted_for { print " $LOOPCOUNT $_\n" } keys %{$LoH->[$count]}; } @y;
    *arguably; because prototypes require that non-explicit subs be the first arg, I can't duplicate the for syntax, and of course I can't make a predicate form. Maybe it should be a map replacement instead.

    Update: And of course last and next won't work with it.will give you warnings.


    Caution: Contents may have been coded under pressure.
Re: Automatic Loop Counter not in perl
by ambrus (Abbot) on Aug 23, 2007 at 09:28 UTC

    There's no need for a special variable for you can implement this perfectly well in perl.

    #!perl use warnings; use strict; our $LOOPCOUNTER; sub forc (&@) { my($func, @arr) = @_; for $LOOPCOUNTER (0 .. @arr - 1) { &$func(local $_ = $arr[$LOOPCOUNTER]); } } forc { my($last) = @_; print "($LOOPCOUNTER\n"; forc { print " $LOOPCOUNTER $_ $last\n"; } qw"andy john"; print ")$LOOPCOUNTER\n" } qw"smith brown"; __END__

    Output:

    (0 0 andy smith 1 john smith )0 (1 0 andy brown 1 john brown )1

    (Update: yeah, I didn't read the replies again. Roy Johnson has already said the same thing.)

Re: Automatic Loop Counter not in perl
by ysth (Canon) on Aug 22, 2007 at 00:37 UTC
    Useful, but the hidden use of a global variable is a drawback. And other ways to do it involve adding more syntax than I think the feature warrants. But I'll throw out my ideas anyway.
    Adverb on the loop keyword
    print "$_ at index $counter\n" for :counter(my $counter) @x;
    Attribute on a variable
    my $counter:loopcounter; # automatically used for all loops in the inn +ermost scope print "$_ at index $counter\n" for @x; print "$_\n" for @y; print "\@x and \@y had $counter total elements\n";
Re: Automatic Loop Counter not in perl
by snoopy (Curate) on Aug 22, 2007 at 23:42 UTC
    Another approach is to use an in-memory open (Perl 5.8.0+) thus enabling $.:
    #!/usr/bin/perl use warnings; use strict; open (my $loop_fh, '<', \"Apples\nOranges\nBannanas\nKiwis") or die "unable to open: $!"; while (<$loop_fh>) { chomp; print "item $. is $_\n"; }

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (4)
As of 2024-03-19 07:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found