scalaindexoutofboundsexceptionlistbuffer

Listbuffer behaviour unintuitive... how to solve?


I have a ListBuffer[MyClass] and use it as a queue.

Now consider the following code:

private def buildChunks(): Unit =
{
    for(a <- 0 until buildQueue.size)
    {
      val chunk: Chunk = buildQueue(a)
      chunk.init()
    //  buildQueue -= chunk
    //  buildQueue.remove(a)
    }
}

My problem with understanding boils down to these two lines:

buildQueue -= chunk
buildQueue.remove(a)

Both of them yield an ArrayOutOfBoundsException if used (of course mutually exclusive I did not use them both at once!)

As I said (and as the name implies) the ListBuffer is used as a queue, so if one item is processed I want to remove it from the list.

I do not understand why any of these lines throws an ArrayOutOfBoundsException

How am I supposed to remove an Item then?

If you can make me understand this, I would gladly use a prettier approach such as:

val chunk: Chunk = buildQueue.remove(a)

but of course this doesn't work


Solution

  • You problem would be that you are changing a mutable collection while iterating over it's values.

    In for(a <- 0 until buildQueue.size) the value of buildQueue.size is evaluated once because 0 until buildQueue.size creates an immutable Seq[Int].

    Now, if your list buffer initially has a size of 5 and you remove one element, it will end up with a size of 4. However, your loop will iterate until index 4, which is no longer present in the list buffer.

    One way to fix this would be using a recursive function:

    private def buildChunks(): Unit = {
      @tailrec
      def buildHead(): Unit = {
        buildQueue.headOption match {
          case None ⇒
            () // end of recursion
          case Some(chunk) ⇒
            chunk.init()
            buildQueue -= chunk
            buildHead()
        }
      }
    
      buildHead()
    }
    

    UPDATE:

    As pointed out by Teolha you could as well just do:

    private def buildChunks(): Unit = {
       buildQueue.foreach(_.init())
       buildQueue.clear()
    }
    

    which is much shorter and probably more efficient.

    However, it would not build chunks appended to the queue concurrently while buildChunks() is executed and in fact might remove any chunks added after foreach is started.