why does this iterable implementation produce a st

2019-08-08 08:00发布

问题:

I am trying to implement an iterable for a linked list. But this implementation throws stack overflow error. What am I doing wrong here?

The iterable implementation throws error, even before I call any function on the class.

I initially thought, it could be because of using def instead of val for the nodeValue and tailList. But that didnt solve the problem.

I tried to change the iterator to loop over Int instead of LinkedList. I still get the same error. Thanks in advance!!

trait LinkedList extends Iterable[LinkedList]{
  def nodeValue: Int
  def tailList: LinkedList
}

class Node(val nodeValue: Int, val tailList: LinkedList) extends LinkedList {

  override def iterator: Iterator[LinkedList] = Iterator
    .iterate(this: LinkedList)(_.tailList)
    .takeWhile(_ != Nil)
}

object Nil extends LinkedList {
  def nodeValue= throw new IllegalAccessException("head of Nil")
  def tailList = throw new IllegalAccessException("tail of Nil")

  override def iterator: Iterator[LinkedList] = Iterator.empty
}

val singleLinkedList = new Node(1,Nil)
val chainedLinkedList = new Node(2,singleLinkedList)

//Printing this first Time
//chainedLinkedList.foreach(println)

Output:

java.lang.StackOverflowError
    at scala.collection.AbstractIterable.<init>(LinkedListIterable.sc:50)
    at scala.collection.AbstractSeq.<init>(LinkedListIterable.sc:37)
    at scala.collection.mutable.AbstractSeq.<init>(LinkedListIterable.sc:44)
    at scala.collection.mutable.StringBuilder.<init>(LinkedListIterable.sc:28)
    at scala.collection.mutable.StringBuilder.<init>(LinkedListIterable.sc:45)
    at scala.collection.mutable.StringBuilder.<init>(LinkedListIterable.sc:50)
    at scala.collection.TraversableOnce.mkString(LinkedListIterable.sc:319)
    at scala.collection.TraversableOnce.mkString$(LinkedListIterable.sc:318)
    at #worksheet#.Node.mkString(LinkedListIterable.sc:6)
    at scala.collection.TraversableLike.toString(LinkedListIterable.sc:596)
    at scala.collection.TraversableLike.toString$(LinkedListIterable.sc:596)
    at #worksheet#.Node.toString(LinkedListIterable.sc:6)
    at java.lang.String.valueOf(LinkedListIterable.sc:2990)
    at scala.collection.mutable.StringBuilder.append(LinkedListIterable.sc:196)
    at scala.collection.TraversableOnce.$anonfun$addString$1(LinkedListIterable.sc:355)
    at scala.collection.Iterator.foreach(LinkedListIterable.sc:925)
    at scala.collection.Iterator.foreach$(LinkedListIterable.sc:925)
    at scala.collection.AbstractIterator.foreach(LinkedListIterable.sc:1413)
    at scala.collection.IterableLike.foreach(LinkedListIterable.sc:67)
    at scala.collection.IterableLike.foreach$(LinkedListIterable.sc:66)
    at #worksheet#.Node.foreach(LinkedListIterable.sc:6)
    at scala.collection.TraversableOnce.addString(LinkedListIterable.sc:353)
    at scala.collection.TraversableOnce.addString$(LinkedListIterable.sc:349)
    at #worksheet#.Node.addString(LinkedListIterable.sc:6)
    at scala.collection.TraversableOnce.mkString(LinkedListIterable.sc:319)
    at scala.collection.TraversableOnce.mkString$(LinkedListIterable.sc:318)
    at #worksheet#.Node.mkString(LinkedListIterable.sc:6)
    at scala.collection.TraversableLike.toString(LinkedListIterable.sc:596)
    at scala.collection.TraversableLike.toString$(LinkedListIterable.sc:596)
    at #worksheet#.Node.toString(LinkedListIterable.sc:6)
    at java.lang.String.valueOf(LinkedListIterable.sc:2990)
    at scala.collection.mutable.StringBuilder.append(LinkedListIterable.sc:196)
    at scala.collection.TraversableOnce.$anonfun$addString$1(LinkedListIterable.sc:355)
    at scala.collection.Iterator.foreach(LinkedListIterable.sc:925)
Output exceeds cutoff limit.

Update:

The problem is as mentioned by @Dima, Iterator, iterates through some value in container and not the container itself. Once that was clear, I modified the code, which ran fine as below.

trait LinkedList extends Iterable[Int]{
  val nodeValue: Int
  val tailList: LinkedList
  override def toString(): String = this.mkString(" -> ")
}

class Node(val nodeValue: Int, val tailList: LinkedList) extends LinkedList {

  override def iterator: Iterator[Int] = Iterator
    .iterate(this: LinkedList)(_.tailList)
    .takeWhile(_ != Nil)
    .map(_.nodeValue)
}

object Nil extends LinkedList {
  lazy val nodeValue= throw new IllegalAccessException("head of Nil")
  lazy val tailList = throw new IllegalAccessException("tail of Nil")

  override def iterator: Iterator[Int] = Iterator.empty
}

val singleLinkedList = new Node(1,Nil)
val chainedLinkedList = new Node(2,singleLinkedList)

//Printing this first Time
chainedLinkedList.foreach(println)

回答1:

Well, the problem is, that normally Iterable.iterator iterates over the values in the container, not the actual container nodes.

What happens here, is when you type new Node(1, Nil) in REPL, it tries to print out the result, and calls Node.toString, which is implemented on Iterable to iterate through the entire container, and convert all values to string.

So, it calls your iterator, gets the next (what it thinks is) element, and tries to convert it to a String. But what your iterator returns isn't the value stored in the node, but the node itself, so, when toString is called on it, it is still the same Iterable.toString, that ends up calling the same iterator, that'll return the same node, that it'll try to convert to string ... etc. Infinite recursion.



标签: scala