how to cycle through list indefinitely and lazily in Raku?

267 views Asked by At

This is mostly to geek out on how awesome Raku is.

Question

Are there built-in methods that will take a list and cycle through it indefinitely, producing, say, the lazy list

a, b, c, a, b, c, ...

out of (a, b, c)? Nothing in the documentation on lists seems to obviously do the trick.

Possible solutions

I can think of at least a couple.

The more plodding down-to-earth method would be to map @array[<variable> mod length-of-@array] over the lazy range 0..Inf. In the perl6 REPL:

> my @ar=<a b c>
[a b c]
> (0..Inf).map({ @ar[$_ % @ar.elems] }).[0..100]
(a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a ...)

A cooler (I think) solution would have you first turn your list into a slip, and then apply the repetition operator to that slip indefinitely:

> my @ar=<a b c>
[a b c]
> (|@ar xx *).[0..100]
(a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a ...)

Conclusion

Even though I can achieve this (and the last solution in particular is very pithy and satisfying), what I am wondering is whether I'm missing anything built in specifically for this purpose.


Edit Re: the accepted answer

This is to elaborate slightly on the answer by @Brad Gilbert. The «~» operator wraps string concatenation ~ in the « » hyper operator, which results in applying the wrapped binary operator to elements selected sequentially from the two lists.

So to achieve what I wanted (list-cycling to a desired length, like say 100), one would do

<a b c> <<~>> ("" xx 100)

This produces

(a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a b c a)

(100 entries). It does have a couple of drawbacks though:

  • it coerces the list entries to strings, due to the application of ~
  • it doesn't actually produce a lazy infinite list:
<a b c> <<~>> ("" xx *)

returns

List on right side of hyperop of infix:<~> is known to be infinite
  in block <unit> at <unknown file> line 1
3

There are 3 answers

3
Brad Gilbert On BEST ANSWER

When it can be written this short, why add a feature just for that. Especially since it is probably a rare event that you would need such a thing.

|< a b c > xx *

Well there is one exception, if you use something like «~» it extend it out for you.

< a b c > «~» (1..10)
# (a1 b2 c3 a4 b5 c6 a7 b8 c9 a10)
3
metagib On

I don't know if I understood the problem. But what I understood that you need a method to produce infinite list following a pattern. Well if that's the problem in the book "Think in Raku" comes with something that may help, in the part 14.5 (adapted from how to do a Fibonacci sequence)

("a","b","c", -> $a, $b, $c {$a} ...* )

this element is the pure-list, if you want the extract values you need something like more conventional on list (like assign & extract values)

my @list = ("a","b","c", -> $a, $b, $c {$a} ...* )
@list[0..100]
@list[91]

I think that's it's not good idea using map in a Inf context, but please tell me if I missed something, i still learning about this awesome language too !!

0
elcaro On

Your first "down-to-earth" example can be written as (^Inf).map({ @ar[$_ % *] })

Also, the slip and repetition works fine, but only for reasonably sized lists.

sub bad-cycle(@xs) {
    |@xs xx Inf
}

my $big-list = ^1e6;
say bad-cycle($big-list).head(100);    # Hangs for several seconds

A better option would be a map like shown above, or a gather loop like so

sub cycle(@xs) {
    gather loop { .take for @xs }
}

say cycle($big-list).head(100);    # Instant output