Michael Lorton
Michael Lorton

Reputation: 44376

How am I *supposed* to use a scala.collection.immutable.Queue?

I have what I would think is the most common case for processing a queue. I will read off the front of the queue, act on the element (which may cause more elements to be added to the queue), and then loop until the queue is empty.

  1. My first instinct was foreach, but no, apparently a queue (even a mutable one) is strict and the foreach loops over all the elements that are in the queue when the iteration starts.
  2. I cannot figure out the syntax for a while loop.

You'd think that it would be something like

while (!q.isEmpty) {
   var (e, q) = q.dequeue
   ... }

would work, except that I'm redeclaring q. This does work:

while (!q.isEmpty) {
   var (e, q1) = q.dequeue
   q = q1
   ... }

but man, does it look wrong ...

Upvotes: 28

Views: 9645

Answers (3)

NthPortal
NthPortal

Reputation: 382

Processing a Queue in a while loop can be done without a duplicate var/val as follows:

var q = Queue("foo", "bar", "baz")
while (q.nonEmpty) {
  val e = q.head
  q = q.tail
  // Do something with `e` here
}

(I am aware that this answer is 7 years late, but I thought it a valuable alternative nonetheless.)

Upvotes: 9

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297155

While Rex Kerr's answer is good, iterators are mutable. Here's a truly immutable solution, modeled very closely on the code in Rex Kerr's own answer.

val q0 = collection.immutable.Queue("1","Two","iii")
@annotation.tailrec def processQueue(queue: collection.immutable.Queue[String]): Unit = if (queue.nonEmpty) {
    val (element, rest) = queue.dequeue
    println("I just dequeued "+element)
    if (element.length != 2) processQueue(rest.enqueue(".."))
    else processQueue(rest)
}
processQueue(q0)

Upvotes: 14

Rex Kerr
Rex Kerr

Reputation: 167871

Here's one way to avoid any vars at all:

val q0 = collection.immutable.Queue("1","Two","iii")
Iterator.iterate(q0) { qi =>
  val (e,q) = qi.dequeue
  println("I just dequeued "+e)  // Your side-effecting operations go here
  if (e.length!=2) q.enqueue("..")  // Your changes to the queue go here
  else q
}.takeWhile(! _.isEmpty).foreach(identity)

You start with the initial queue, q0, and then on the qith step, you dequeue something and produce a new queue if need be, returning that for the next step.

All you have left is the stopping condition (not empty), and then since this just defines a process, not the actual action, you have to run it (using a no-op foreach, for example).

Upvotes: 17

Related Questions