Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl 6 to show index while looping through a list

Tags:

raku

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;
like image 628
Eugene Barsky Avatar asked Oct 11 '17 14:10

Eugene Barsky


3 Answers

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
like image 122
Håkon Hægland Avatar answered Jan 02 '23 19:01

Håkon Hægland


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;
like image 27
Brad Gilbert Avatar answered Jan 02 '23 20:01

Brad Gilbert


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
like image 35
Rubio Terra Avatar answered Jan 02 '23 21:01

Rubio Terra