5

Here is the code:

my @s=<a b c d>;
for @s.kv {
    for ($^k ... @s.elems) {
        printf("%s ", $^v);
    }
    printf("\n");
}

Expected output is:

# a b c d
# b c d
# c d
# d

But it gives this error (possibly among others)

key 0, val 1 Too few positionals passed; expected 2 arguments but got 1

It looks like the positional variables of the main loop $^k and $^v can't be used inside the inner loop. How to fix it? Thanks. Update: Typo inside inner loop fixed

Lars Malmsteen
  • 1,819
  • 2
  • 13

2 Answers2

6

So for what you want to do I'd approach it like this :

my @s = <a b c d>;
for ^@s.elems -> $start-index {
    for @s[$start-index..*] -> $value {
        printf("%s ", $value );
    }
    print("\n");
}

Though really I'd do this.

my @s = <a b c d>;
(^@s.elems).map( { @s[$_..*].join(" ").say } )

Get the range from 0 to the number of elements in the array. Then the slice from there to the end for each, join on spaces and say.

A note on variables like $^k these are scoped to the current block only (hence why your above code is not working). Generally you only really want to use them in map, grep or other such things. Where possible I'd always advise naming your variables, this makes them scoped inside inner blocks as well.

Scimon Proctor
  • 4,183
  • 20
  • 21
  • Thank you for the answer. How does the `^` in front of `^@s.elems` operate? – Lars Malmsteen Jan 07 '20 at 14:40
  • 1
    It's a short cut for the Range of 0..^@s.elems . (You don't want @s.elems itself because that's out of bounds). – Scimon Proctor Jan 07 '20 at 14:41
  • 3
    `@s[$_..*].join(" ").say for ^@s` – raiph Jan 07 '20 at 18:00
  • The last paragraph added by @ScimonProctor as to why the `$^k` variable didn't work is quite helpful. And both one-liner solutions (by @ScimonProctor and @raiph) help learn the real language. And as for the usage of the `^` operator, I actually knew that but somehow it got out of my mind. I will go through its explanations, they're valuable to me. Thank you. – Lars Malmsteen Jan 07 '20 at 18:58
6

Scimon Proctor's answer is essentially correct, but I'll try to explain why your example does not work. For starters, kv returns "an interleaved sequence of indexes and values", so this:

my @s=<a b c d>;
.say for @s.kv;

prints

0
a
1
b
2
c
3
d

Essentially, you're doing one turn of the loop for every key and value. Grouping them in pairs using rotor might be closer to what you're looking for:

.say for @s.kv.rotor(2)

which will return:

(0 a)
(1 b)
(2 c)
(3 d)

Since with this we got the value couple with the index, we can do...

my @s=<a b c d>;
for @s.kv.rotor(2) -> ($k, $) {
    "{@s[$_]} ".print for ($k..^@s.elems);
    printf("\n");
}

Please note that there was also an error in the inner loop, whose range went beyond the actual indices in @s. But, again, Scimon's answer that uses maps is much shorter, idiomatic and straightforward. This one is just kind of dwimming your original program. As a matter of fact, we are throwing away the values, so this would actually be:

my @s=<a b c d>;
for @s.keys -> $k {
    "{@s[$_]} ".print for ($k..^@s.elems);
    printf("\n");
}

No need to use kv at all, and just make do with the keys.

jjmerelo
  • 19,108
  • 5
  • 33
  • 72
  • 1
    @raiph there's actually a better way to do it, I just edited it. But you're right, as always, and thanks. – jjmerelo Jan 07 '20 at 18:35
  • 1
    @jmerelo :) :) :) – raiph Jan 07 '20 at 18:55
  • 1
    Good to see an explanation on why the `.kv` construct didn't work. Using `.kv` with the `rotor is a good idea, too. – Lars Malmsteen Jan 07 '20 at 19:03
  • 2
    Alternatively, for the `rotor` example, one could let the block's signature grabs however many arguments are needed for each turn of the loop (e.g., `for @s.kv -> $k, $ { ... }`). As always, TIMTOADY though ;-)! – Luis F. Uceta Jan 07 '20 at 20:13