When looping through a list (or an array), is there a way to know the index of the current element inside the loop?
Of course, the problem can be solved by looping through indices:
my @aa = 8 .. 12;
say "$_\t@aa[$_]" for 0 ..^ @aa.elems;
But maybe something like the following is possible (I marked with .CURRENT_INDEX
the method I'm looking for)?
my @aa = 8 .. 12;
say $_.CURRENT_INDEX\t$_ for @aa;
To get the loop index of the current element of loop over a list, you can use the .kv
method of a list. It returns an interleaved sequence of indexes and values:
my @aa = 8 .. 12;
for @aa.kv -> $i, $_ { say "$i: $_" }
Output:
0: 8
1: 9
2: 10
3: 11
4: 12
TLDR: Use .kv
or .pairs
.
This is sort of what is really happening under the hood:
my @aa = 8 .. 12;
my \iterator = @aa.iterator;
while ($_ := iterator.pull-one) !=:= IterationEnd {
say $_
}
The value in iterator
in this case is an anonymous class that does the Iterator role.
An Iterator may or may not have any way to know how many values it has produced. For example the Iterator for .roll(*)
doesn't need to know how many values it has produced so far, so it doesn't.
It is possible for an Iterator to implement a method that returns its current index.
my @aa = 8 .. 12;
my \iterator = class :: does Iterator {
has $.index = 0; # declares it as public (creates a method)
has @.values;
method pull-one () {
return IterationEnd unless @!values;
++$!index; # this is not needed in most uses of an Iterator
shift @!values;
}
}.new( values => @aa );
say "{iterator.index}\t$_" for Seq.new: iterator;
1 8
2 9
3 10
4 11
5 12
You could also do it in a higher level construct;
my @aa = 8 .. 12;
my $index = 0;
my $seq := gather for @aa { ++$index; take $_ };
say "$index\t$_" for $seq;
To get $_.CURRENT-INDEX
to work requires wrapping the result.
class Iterator-Indexer does Iterator {
has Iterator $.iterator is required;
has $!index = 0;
method pull-one () {
my \current-value = $!iterator.pull-one;
# make sure it ends properly
return IterationEnd if current-value =:= IterationEnd;
# element wrapper class
class :: {
has $.CURRENT-INDEX;
has $.value;
# should have a lot more coercion methods to work properly
method Str () { $!value }
}.new( CURRENT-INDEX => $!index++, value => current-value )
}
}
multi sub with-index ( Iterator \iter ){
Seq.new: Iterator-Indexer.new: iterator => iter;
}
multi sub with-index ( Iterable \iter ){
Seq.new: Iterator-Indexer.new: iterator => iter.iterator;
}
my @aa = 8 .. 12;
say "$_.CURRENT-INDEX()\t$_" for with-index @aa.iterator;
# note that $_ is an instance of the anonymous wrapper class
Again with a higher level construct:
my @aa = 8 .. 12;
my \sequence := @aa.kv.map: -> $index, $_ {
# note that this doesn't close over the current value in $index
$_ but role { method CURRENT-INDEX () { $index }}
}
say "$_.CURRENT-INDEX()\t$_" for sequence;
I would argue that you should just use .pairs
if you want it something like this. (or use .kv
but that basically requires using the block form of for
with two parameters)
my @aa = 8 .. 12;
say "$_.key()\t$_.value()" for @aa.pairs;
Here's another way, using your own index variable:
my @aa = 8..12;
say $++, ": $_" for @aa;
Output:
0: 8
1: 9
2: 10
3: 11
4: 12
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With