79

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nathan Fellman
  • 122,701
  • 101
  • 260
  • 319

13 Answers13

119

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";
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
trendels
  • 4,747
  • 3
  • 26
  • 16
  • 5
    This is not what you want if array index sequence is 2, 5, 8, 19 with gaps in sequence. I believe the question was something regarding index of current element. – Andy N Sep 29 '12 at 15:06
  • The only way to generalize for an array with gaps is to put the line, `next unless defined $x[$i];` at the start of the loop. This seems to be true also for all the more risky solutions involving while/each and for/keys. Apparently the array iterator sees each sequential index whether it exists or not. – Arnold Cross Apr 25 '20 at 03:28
  • I just thought of a better way to write that line: `$x[$i] // next;` – Arnold Cross Apr 25 '20 at 22:32
  • $# syntax is not supported perl 5.30.. – Han.Oliver Nov 30 '22 at 01:24
48

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[$index]\n";
}

In Perl 5.10, you use state to declare a variable that never gets reinitialized (unlike ones created with my). This lets you keep the $index variable in a smaller scope, but it 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
  • 64,182
  • 22
  • 135
  • 226
21

perldoc perlvar does not seem to suggest any such variable.

Alan Haggai Alavi
  • 72,802
  • 19
  • 102
  • 127
  • 6
    [*AndrewKS*](http://stackoverflow.com/users/444794/andrewks): This certainly is *not* against the spirit of StackOverflow. If you had tried `perldoc perlvar`, you would know why. Only variables listed in `perldoc perlvar` exist. Such a variable, as I already stated, does not exist. – Alan Haggai Alavi Apr 02 '12 at 08:20
  • 6
    This *is* against the spirit of stackoverflow, because the spirit is to provide an answer that can answer a question in it's entirety, without external resources. Providing optional resources such as links or terminal commands to run for learning is a bonus, but not the *spirit*. The fact that you had to provide further information in a comment is proof that this answer is not enough on it's own. – SgtPooki Oct 16 '15 at 21:58
  • 1
    This is basically the same as a link-only answer. – jinawee Oct 15 '19 at 08:25
  • Perhaps expand your answer? – Peter Mortensen Nov 20 '19 at 14:12
11

It can be done with a while loop (foreach doesn't support this):

my @arr = (1111, 2222, 3333);

while (my ($index, $element) = each(@arr))
{
   # You may need to "use feature 'say';"
   say "Index: $index, Element: $element";
}

Output:

Index: 0, Element: 1111
Index: 1, Element: 2222
Index: 2, Element: 3333

Perl version: 5.14.4

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Venkata Raju
  • 4,866
  • 3
  • 27
  • 36
  • Perldoc says not to use While-Each --- It's easy enough to explicitly reset the iterator before starting a loop, but there is no way to insulate the iterator state used by a loop from the iterator state used by anything else that might execute during the loop body. To avoid these problems, use a foreach loop rather than while-each.--- https://perldoc.perl.org/functions/each.html – Able Mac Jul 16 '19 at 04:15
8

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";
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
codehead
  • 2,077
  • 16
  • 20
  • 5
    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 Jun 10 '09 at 13:25
  • 2
    foreach and for are /not/ interchangeable, as this instance shows. – Matthew Flaschen Jun 10 '09 at 14:28
  • @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 Jun 10 '09 at 14:35
  • 9
    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. – user55400 Jun 10 '09 at 14:37
  • 1
    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 Jun 10 '09 at 14:43
  • 4
    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 Jun 11 '09 at 05:18
5

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hynek -Pichi- Vychodil
  • 26,174
  • 5
  • 52
  • 73
  • 1
    0+ is unneedded; postincrement returns 0 if the variable incremented was undef. – ysth Jun 10 '09 at 13:05
  • @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 Jun 10 '09 at 16:17
  • This answer is arguably more readable than the `for` loop syntax of `for(my $index; $index <= $#x; $index++){}`. – Able Mac Jul 16 '19 at 04:22
4

Yes. I have checked so many books and other blogs... The conclusion is, there isn't any system variable for the loop counter. We have to make our own counter. Correct me if I'm wrong.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
surendra ben
  • 179
  • 1
  • 4
  • You are right, there is no accessible system counter. --- And "make our own counter" can be done many ways; the most common is probably the `for` (aka `foreach`) with the optional long syntax of `for($i=0;$i<=$#array;$i++){}`. – Able Mac Jul 16 '19 at 04:23
4

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 example: Array::Iterator

use Array::Iterator;

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

Also see:

Community
  • 1
  • 1
draegtun
  • 22,441
  • 5
  • 48
  • 71
  • I would replace the links to the distributions with a link of the form: http://search.cpan.org/perldoc/Array::Iterator – Brad Gilbert Jul 14 '10 at 03:52
  • @Brad Gilbert: Changed. Though personally I prefer the top level (home) view presented by http://search.cpan.org/dist/Array-Iterator – draegtun Jul 14 '10 at 07:46
  • 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 Jul 14 '10 at 20:01
  • @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 Jul 15 '10 at 08:37
3

Oh yes, you can! (sort of, but you shouldn't). each(@array) in a scalar context gives you the current index of the array.

@a = (a..z);
for (@a) { 
  print each(@a) . "\t" . $_ . "\n"; 
}

Here each(@a) is in a scalar context and returns only the index, not the value at that index. Since we're in a for loop, we have the value in $_ already. The same mechanism is often used in a while-each loop. Same problem.

The problem comes if you do for(@a) again. The index isn't back to 0 like you'd expect; it's undef followed by 0,1,2... one count off. The perldoc of each() says to avoid this issue. Use a for loop to track the index.

each

Basically:

for(my $i=0; $i<=$#a; $i++) {
  print "The Element at $i is $a[$i]\n";
}

I'm a fan of the alternate method:

my $index=0;
for (@a) {
  print "The Element at $index is $a[$index]\n";
  $index++;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Able Mac
  • 846
  • 7
  • 8
  • Nice try, but no. each does not access the same iterator that the for loop is using. The iterator of each is a part of the array or hash. for creates its own list of aliases to the array and iterates over that. You can see what I mean by using `each(@a)` again within the loop, for instance `print each(@a)."=< $_ >=".each(@a);`. – Arnold Cross Apr 25 '20 at 03:15
1

Please consider:

print "Element at index $_ is $x[$_]\n" for keys @x;
aaa
  • 11
  • 1
  • I don't think that `keys @x` behaves as you think. I'd expect it to return every even-indexed element. – Nathan Fellman Apr 29 '18 at 08:56
  • `for keys @x` is in a list context, returns the current index, and to get the value you have to reference it `$x[$_]` --- A more readable way to write the exact same thing is `for my $idx (keys @x){print "Element at index $idx is $x[$idx]\n";}` – Able Mac Jul 16 '19 at 04:37
1

Well, there is this way:

use List::Rubyish;

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

See List::Rubyish.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Axeman
  • 29,660
  • 2
  • 47
  • 102
0

I have tried like....

@array = qw /tomato banana papaya potato/;             # Example array
my $count;                                             # Local variable initial value will be 0.
print "\nBefore For loop value of counter is $count";  # Just printing value before entering the loop.

for (@array) { print "\n",$count++," $_" ; }           # String and variable seperated by comma to
                                                       # execute the value and print.
undef $count;                                          # Undefining so that later parts again it will
                                                       # be reset to 0.

print "\nAfter for loop value of counter is $count";   # Checking the counter value after for loop.

In short...

@array = qw /a b c d/;
my $count;
for (@array) { print "\n",$count++," $_"; }
undef $count;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • What would happen if you replace the first line with: my @arr = ('foo', 'bar', 'foo'); – Anon Jun 11 '09 at 08:44
  • It would print 1, 1, 1 since string scalars evaluate to zero when used in number context. –  Jun 12 '09 at 23:37
  • 7
    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 Jan 05 '10 at 09:22