tags:

views:

3131

answers:

8

If I have the following array in Perl:

@x = qw(a b c);

and I iterate over it with foreach, then $_ will refer to the current element in the array:

foreach (@x) {
    print;
}

will print:

abc

Is there a similar way to get the index of the current element, without manually updating a counter? Something such as:

foreach (@x) {
    print $index;
}

where $index is updated like $_ to yield the output:

012
+6  A: 

perldoc perlvar does not seem to suggest any such variable.

Alan Haggai Alavi
+1 for teaching a man how to fish
Paul Dixon
+3  A: 

Not with foreach. If you definitely need the element cardinality in the array use a 'for' iterator.

for($i=0;$i<@x;++$i) {
  print "Element at index $i is ",$x[$i],"\n";
}
codehead
foreach and for are interchangeable synonyms. Some introductory material tries to use for to mean the C-style loop and foreach for the list iterator loop, but that use of terminology isn't going to be familiar to everyone.
ysth
foreach and for are /not/ interchangeable, as this instance shows.
Matthew Flaschen
@Matthew Flaschen It is more correct to say that you can replace the keyword foreach with the keyword for, but not necessarily the other way around.
Chas. Owens
Quoting from 'perldoc perlvar': The "foreach" keyword is actually a synonym for the "for" keyword, so you can use "foreach" for readability or "for" for brevity. endquote. So they *are* interchangeable. Try it.
The keywords are interchangeable -- but the loops themselves have different meanings. A "for" loop is a loop with an incrementing (or decrementing) variable, even if it's introduced with the foreach keyword. A "foreach" loop has an internal iterator, even if it's introduced with the for keyword. The OP wanted access to the iterator, so he wants a "for" loop, regardless of the keyword used.
Andrew Barnett
a C-style for loop (that y'all keep calling a "for loop") doesn't have to increment/decrement a variable, e.g.: for (my $iter = new_iter(); $iter; $iter = $iter->next() ) { ... }
ysth
+15  A: 

Like codehead said, you'd have to iterate over the array indices instead of its elements. I prefer this variant over the C-style for loop:

for my $i (0 .. $#x) {
    print "$i: $x[$i]\n";
}
trendels
+1 for not using the ugly C-style for-loop.
fengshaun
+2  A: 

No, you must make your own counter. Yet another example:

my $index;
foreach (@x) {
    print $index++;
}

when used for indexing

my $index;
foreach (@x) {
    print $x[$index]+$y[$index];
    $index++;
}

And of course you can use local $index; instead my $index; and so and so.

EDIT: Updated according to first ysth's comment.

Hynek -Pichi- Vychodil
0+ is unneedded; postincrement returns 0 if the variable incremented was undef.
ysth
You don't need local there. my would work just fine.
ysth
@ysth: You can be surprised when someone somewhere in application use same global $index variable as you. But if you write short script and you assume ... no, don't do it.
Hynek -Pichi- Vychodil
A: 

Well there is this way:

use List::Rubyish;

$list = List::Rubyish->new( [ qw<a b c> ] );
$list->each_index( sub { say "\$_=$_" } );

see List::Rubyish

Axeman
+7  A: 

In Perl prior to 5.10, you can say

#!/usr/bin/perl

use strict;
use warnings;

my @a = qw/a b c d e/;

my $index;
for my $elem (@a) {
    print "At index ", $index++, ", I saw $elem\n";
}

#or

for my $index (0 .. $#a) {
    print "At index $index I saw $a[$elem]\n";
}    

In Perl 5.10, you use state to declare a variable that never gets reinitialized (unlike ones create with my). This lets you keep the $index variable in a smaller scope, but can lead to bugs (if you enter the loop a second time it will still have the last value):

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

my @a = qw/a b c d e/;

for my $elem (@a) {
    state $index;
    say "At index ", $index++, ", I saw $elem";
}

In Perl 5.12 you can say

#!/usr/bin/perl

use 5.012; #this enables strict
use warnings;

my @a = qw/a b c d e/;

while (my ($index, $elem) = each @a) {
    say "At index $index I saw $elem";
}

But be warned: you there are restrictions to what you are allowed to do with @a while iterating over it with each.

It won't help you now, but in Perl 6 you will be able to say

#!/usr/bin/perl6

my @a = <a b c d e>;
for @a Z 0 .. Inf -> $elem, $index {
    say "at index $index, I saw $elem"
}

The Z operator zips the two lists together (i.e. it takes one element from the first list, then one element from the second, then one element from the first, and so on). The second list is a lazy list that contains every integer from 0 to infinity (at least theoretically). The -> $elem, $index says that we are taking two values at a time from the result of the zip. The rest should look normal to you (unless you are not familiar with the say function from 5.10 yet).

Chas. Owens
I don't like the section on `state`, I think it would be better served with `{ my $index; ... }`.
Brad Gilbert
A: 

You shouldn't need to know the index in most circumstances, you can do this

my @arr = (1, 2, 3);
foreach (@arr) {
    $_++;
}
print join(", ", @arr);

In this case, the output would be 2, 3, 4 as foreach sets an alias to the actual element, not just a copy.

Charlie Somerville
What would happen if you replace the first line with:my @arr = ('foo', 'bar', 'foo');
Anon
It would print 1, 1, 1 since string scalars evaluate to zero when used in number context.
Charlie Somerville
I know that I don't need the index in most circumstances. The question is about the best way to get it when I do need it.
Nathan Fellman
+1  A: 

autobox::Core provides among many more things a handy for method:

use autobox::Core;

['a'..'z']->for( sub{
    my ($index, $value) = @_;
    say "$index => $value";
});

Alternatively have a look at an iterator module, for eg: Array::Iterator

use Array::Iterator;

my $iter = Array::Iterator->new( ['a'..'z'] );
while ($iter->hasNext) {
    $iter->getNext;
    say $iter->currentIndex . ' => ' . $iter->current;
}

Also see:

/I3az/

draegtun
I would replace the links to the distributions with a link of the form:http://search.cpan.org/perldoc/Array::Iterator
Brad Gilbert
@Brad Gilbert: Changed. Though personally I prefer the top level (home) view presented by http://search.cpan.org/dist/Array-Iterator
draegtun
I would agree that it is sometimes useful to point to the Dist view instead of the perldoc view. Also just because I *would* replace the links, doesn't necessarily mean that you *should*.
Brad Gilbert
@Brad Gilbert: This is a good example of TIMTOWTDI in perl perhaps? :) Anyway its no problem and if the _perldoc_ link is becoming more prevalent on SO then I'm happy to switch to using it.
draegtun