25

Is there a Round Robin Queue available in Scala Collections?

I need to repeatedly iterate a list that circles through itself

val x = new CircularList(1,2,3,4)
x.next (returns 1)
x.next (returns 2)
x.next (returns 3)
x.next (returns 4)
x.next (returns 1)
x.next (returns 2)
x.next (returns 3)

... and so on

Cœur
  • 37,241
  • 25
  • 195
  • 267
user2780187
  • 677
  • 7
  • 16

4 Answers4

52

It's pretty easy to roll your own with continually and flatten:

scala> val circular = Iterator.continually(List(1, 2, 3, 4)).flatten
circular: Iterator[Int] = non-empty iterator

scala> circular.take(17).mkString(" ")
res0: String = 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4

There's also a continually method on Stream—just be careful not to hold onto a reference to the head of the stream if you're going to be generating lots of elements.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • 1
    This is a better answer as you are not holding up resources when compared to Stream – Jatin Nov 04 '13 at 17:43
  • @Jatin good point, I edited my answer. I agree, this version is less error-prone. – gourlaysama Nov 04 '13 at 18:17
  • I am unfortunately working with scala 2.9.2 :) and iterator is a part of 2.10 .. Is there a backward compatible version? – user2780187 Nov 04 '13 at 18:38
  • @user2780187: `Iterator`'s been around a long time—I just tested this code in 2.9.2 and it works as expected. – Travis Brown Nov 04 '13 at 18:44
  • 1
    Note that `Stream.continually` doesn't make a circular structure (constant memory); it makes an endlessly repeating one (potentially unbounded memory use). – Seth Tisue Nov 05 '15 at 12:43
  • Why did that iterator return the last element first for take()? – Tom Jun 09 '22 at 18:49
12

You can very easily create a circular list using a Stream.

scala> val l = List(1, 2, 3, 4).toStream
l: scala.collection.immutable.Stream[Int] = Stream(1, ?)

scala> def b: Stream[Int] = l #::: b
b: Stream[Int]

scala> b.take(20).toList
res2: List[Int] = List(1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4)

Edit: you want to make sure to define the repeated part beforehand, once and only once, to avoid blowing the heap (structural sharing in Stream). As in:

def circular[A](a: Seq[A]): Stream[A] = {
  val repeat = a.toStream
  def b: Stream[A] = repeat #::: b
  b
}
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
gourlaysama
  • 11,240
  • 3
  • 44
  • 51
  • I am unfortunately working with scala 2.9.2 :) and iterator is a part of 2.10 .. Is there a backward compatible version? – user2780187 Nov 04 '13 at 18:38
  • Hum, I just tested, and both versions (`Stream` and `Iterator`) work fine in 2.9.2. – gourlaysama Nov 04 '13 at 18:42
  • I would say that the solution provided by @TravisBrown is a better fit in this case, as `Iterator` provides a `next` method. – falconepl Mar 15 '15 at 10:17
2

Version more concentrated on getting new element on every execution.

val getNext: () => Int = {
  def b: Stream[Int] = List(1, 2, 3, 4).toStream #::: b
  var cyclicIterator: Stream[Int] = b
  () => {
    val tail = cyclicIterator.tail
    val result = tail.head
    cyclicIterator = tail
    result 
  }
} // could be written more sexy?

In your problem you can use it like:

for(i <- 1 to 10) yield getNext()
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Waldemar Wosiński
  • 1,490
  • 17
  • 28
0

This is ugly in having an external mutable index, but it does do what's requested:

scala> var i = 0
scala> val ic4 = Iterator.continually { val next = IndexedSeq(1, 2, 3, 4)(i % 4); i += 1; next }
i: Int = 0
ic4: Iterator[Int] = non-empty iterator

scala> ic4 take 10 foreach { i => printf("ic4.next=%d%n", i) }
ic4.next=1
ic4.next=2
ic4.next=3
ic4.next=4
ic4.next=1
ic4.next=2
ic4.next=3
ic4.next=4
ic4.next=1
ic4.next=2

At least it illustrates Iterator.continually. There is also Stream.continually, which has the same signature.

Randall Schulz
  • 26,420
  • 4
  • 61
  • 81