0

Given a class from a legacy library that behaves like an Iterator such that you can traverse it's content with hasNext and next but it does not implement the Iterator interface:

class LegacyIterator[T](iterable: Iterable[T]) {
  val iterator: Iterator[T] = iterable.iterator
  def hasNext: Boolean = iterator.hasNext
  def next(): T = iterator.next()
}

val lagIter: LegacyIterator[Int] = new LegacyIterator(List(1, 2, 3))

What would be your opinion on the most elegant way to traverse such a data structure - idially inside the scope of the function? I've come up with two versions:

One using @tailrec

@tailrec
def lagIterList(accu: List[Int]): List[Int] =
  if (lagIter.hasNext) lagIterList(lagIter.next() :: accu) else accu

val res = lagIterList(Nil).reverse

And one using LazyList

def ll: LazyList[Int] = if(lagIter.hasNext) lagIter.next() #:: ll else LazyList.empty
val res = ll.toList

What would you think would be more Scala idiomatic? The tailrec version is probably slightly faster on larger data structures but the LazyList version makes better use of the collections library.

Or any other ideas?

0

3 Answers 3

2

I would consider an implicit wrapper class extending Iterator[T]:

implicit class LegacyIteratorWrapper[T](private val underlying: LegacyIterator[T]) extends Iterator[T] {
  override def hasNext: Boolean = underlying.hasNext
  override def next(): T = underlying.next()
}

val lagIter: LegacyIterator[Int] = new LegacyIterator(List(1, 2, 3))

// traversal
for (elem <- lagIter) println(elem)

// conversion to list
val lagIterList = lagIter.toList
Sign up to request clarification or add additional context in comments.

4 Comments

Very nice solution. Since I only have one spot where I have to apply this conversion, I was more looking for a slick, nifty solution to be applied inside my functions scope. In general however, I would agree that wrapping the LegacyIterator with an implicit class to gain full access to the collection API, is quite elegant.
What wasn't clear to me from your question is whether you can use the LegacyIterator api directly to traverse it, or if you need to pass it to some other method expecting Iterator.
True, that aspect was not clear. I'll try to make my case more clear and update my question shortly. I was mainly interesed in opinions about weather people would prefer the tailrec or LazyList version to traverse given iterator inside a functions scope. Or if both versions look ugly from a purely subjective point of view.
This might be old-school, but I would turn to a while loop if you only care about traversal in one function
2

Just convert it into a normal iterator:

val normalIterator = Iterator.continually(legacyIterator)
  .takeWhile(_.hasNext)
  .map(_.next)

Then you can use any of the regular scala tooling to traverse/transform it, e.g.: val list = normalIterator.toList

1 Comment

Cool! Thanks for the input. Looks very clean to me.
1

Well, it depends on what you mean by "traverse it". If you only mean, creating a List out of it, I would just use unfold

def toList[T](iter: LegacyIterator[T]): List[T] =
  List.unfold(()) { _ =>
    Option.when(iter.hasNext()) {
      iter.next() -> ()
    }
  }

1 Comment

Cool! Thanks! I didn't know unfold before. I saw it was introduced in 2.13. Very nifty solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.