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

Salute to the wiser monks!

Being a humble apprentice I have come to discover and start using this:
$#{@{$object_ref->{array_ref}}}

In order to do this:
foreach(0..$#{@{$object_ref->{array_ref}}}){ # I need the index! }

Which I think is more elegant than this:
for(my $i=0;$i < scalar(@{$object_ref->{array_ref});$i++){ # I need the index! }

Is there any shortcut or other way for: $#{@{ ?
I suspect there is, because after greping through my cpan build directory I have not been able to find references to anyone using it, so I guess I must be doing something wrong or terribly awkward! And I have a pretty big .cpan/build directory :)

Thank you in advance!
Alejandro

Replies are listed 'Best First'.
Re: A question of perlish elegance on array loop
by Fletch (Bishop) on Nov 30, 2007 at 00:22 UTC

    You don't need the extra @{} In there. Just $#{ $array_ref } is what you want (or $#{ $obj->{ array_ref } } from your example).

    Update: And seconded, unless you've got good reason to need the index for my $elem ( @{ $obj->{ array_ref } } ) { ... } is more idiomatic.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      WOW! Many thanks, it works like a charm

      Since perl it usually chokes (not an array ref on...) on object array references like so: foreach(@$obj->{array_ref}) I figured that it would also choke on $#{$obj->{array_ref}} by not recognizing the array ref in the inner reference. That's why I did'nt bother trying and following my instinct (which usually has worked in Perl!) went ahead on the use of $#{@{
      But that looked pretty awkward so I went and searched a lot CPAN code to see if anyone else was using it, and well, you know the rest of the story :)

      I guess I'll have to study a lot on Introspection and Globs to get the answer to that one, huh?

      Update: Hmmm. After re-reading this, I think I have just answered myself: the same way that @{ "casts" to array is the same way $#{ "casts" to array also. Silly me! Thanks again for making me understand this!

        Not really globs, just references. In general wherever you can have a sigil ($@%*) followed by a variable name you can use a BLOCK returning a reference of the appropriate type; e.g. just like @hash{ qw/x y/ } does a hash slice of %hash you can do @{ $hashref }{ qw/ x y / } to slice the hash referred to by $hashref.

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Re: A question of perlish elegance on array loop
by tachyon-II (Chaplain) on Nov 30, 2007 at 01:01 UTC

    Perl will use scalar context when it expects a scalar. @ary in scalar context is the number of elements so $#ary and @ary-1 are the same. This is perhaps a little more elegant.

    for my $i (0..@{$object_ref->{array_ref}}-1) { # $i got the index }
Re: A question of perlish elegance on array loop
by aquarium (Curate) on Nov 30, 2007 at 00:03 UTC
    it's not apparent why you need the index. if you really don't need the index, because you're processing the whole array anyway, then just do
    foreach(@{$object_ref->{array_ref}}) { # loop code }
    which provides all the values of the array one by one, without using an index.
    the hardest line to type correctly is: stty erase ^H
      lol

      I just knew that it would be questioned whether I needed the index or not and that is precisely why I added the comment at the last minute before posting the question!!!

      I use the reference to make the following code more readable. Here is the relevant code in context. endpoints is just a plain array of program paths, the wheels array is in the same order as endpoints array ( FYI is POEtry ;) )
      sub endpoint_dead { my ($kernel, $heap, $wheel_id) = @_[KERNEL, HEAP, ARG0]; $kernel->post('MAIN-Logger', 'alert', "Endpoint managed by wheel $ +wheel_id died\n"); my $self = $heap->{self}; # find the wheel and restart endpoint foreach(0..$#{@{$self->{endpoints}}}){ my $wheel = $self->{wheels}->[$_]; my $pname = $self->{endpoints}->[$_]; if($wheel->ID == $wheel_id){ [...]


      So, as you can see using the index is more elegant than something like:
      # find the wheel and restart endpoint my $i = 0; foreach(@{$self->{endpoints}){ my $pname = $_; my $wheel = $self->{wheels}->[$i]; [...] $i++; }
      Or even more elegant than the for construct in the original question.

        use List::MoreUtils qw( natatime zip ); ## ... my $iterator = natatime 2, zip @{ $self->{ endpoints } }, @{ $self->{ +wheels } }; while( my( $wheel, $pname ) = $iterator->next( ) ) { if( $wheel->ID == $wheel_id ) { frobnicate( $pname, $wheel ); } }

        Update: If only List::MoreUtils' zip behaved more like Ruby's Array#zip (which returns an Array of Arrays containing the merged items) . . . But then there wouldn't be a good Perl equivalent to auto-unwrapping the separate elements as the variables of the for loop.

        self.wheels.zip( self.endpoints ).each do | wheel, pname | frobnicate( wheel, pname ) end
        </c>

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

        If you really need the index, I find it much nicer to just keep a separate index variable.

        my $idx = 0; foreach my $thing( @{$object_ref->{array_ref}} ) { # do stuff with $thing $idx++; }
        nicely convoluted code :)
        now if there's no other reason....
        foreach(@{$self->{endpoints}}){ my $wheel = $self->{endpoints}->[$_]{wheels}; my $pname = $self->{endpoints}->[$_]; if($wheel->ID == $wheel_id){
        or some such
        or in general, if you find yourself accessing separate arrays/hashes with same index, it's probably a good case for rolling up the structure. data structures change quickly during early development, and these things sneak in when functionality is added.
        although there exist sometimes good reasons for keeping the structures separate.
        the hardest line to type correctly is: stty erase ^H
Re: A question of perlish elegance on array loop
by ikegami (Patriarch) on Nov 30, 2007 at 06:33 UTC

    Shouldn't
    $#{@{$object_ref->{array_ref}}}
    be
    $#{$object_ref->{array_ref}}
    (The last index of the array referenced by $object_ref->{array_ref}.)

    By eliminating multiple instances of $object_ref->{array_ref},

    foreach (0..$#{$object_ref->{array_ref}}) { printf("%d %s\n", $_, $object_ref->{array_ref}[$_]); }

    might be more readable as

    my $array = $object_ref->{array_ref}; foreach (0..$#$array) { printf("%d %s\n", $_, $array->[$_]); }
    You might also be interested in
    my $array = $object_ref->{array_ref}; foreach my $idx (0..$#$array) { foreach my $ele ($array->[$idx]) { printf("%d %s\n", $idx, $ele); } }