Keep a buffered first match and a flag recording whether the initial prefix still needs to be skipped:
class DropUntil[T] private (
    p: T => Boolean,
    iter: Iter[T],
    private var cached: Option[T],
    private var seeking: Boolean) extends Iter[T] {

  def this(p: T => Boolean, iter: Iter[T]) =
    this(p, iter, None, true)

  private def prepare(): Unit = {
    if (seeking) {
      while (cached.isEmpty && iter.hasNext) {
        val x = iter.next()
        if (p(x)) cached = Some(x)
      }
      seeking = false
    }
  }

  def hasNext: Boolean = {
    prepare()
    cached.nonEmpty || iter.hasNext
  }

  def next(): T = {
    prepare()
    cached match {
      case Some(x) => cached = None; x
      case None =>
        if (!iter.hasNext) throw new NoSuchElementException("DropUntil.next")
        iter.next()
    }
  }

  def copy(): Iter[T] = {
    prepare()
    new DropUntil[T](p, iter.copy(), cached, false)
  }
}
While seeking, prepare drops exactly the rejected prefix and caches the first accepted value, including a possible null payload as Some(null). Afterwards the abstract remainder is the cache, if any, followed by the underlying remainder; later values are returned even when they fail p. Repeated hasNext calls do not lose the cached value.
The iterator snapshot copies the underlying cursor and the cached value, then marks the initial search complete. It does not reconstruct a fresh two-argument DropUntil, which could incorrectly discard later values. Resolving the initial search before copying also avoids having two snapshots independently rerun a privately stateful predicate. The wrapper owns consumption of the supplied source cursor. If that source is infinite and never produces an accepted value, seeking cannot finish. This is a prefix-dropping iterator, rather than a repeatedly demand-driven filtering iterator.

Articles by others on the same topic (0)

There are currently no matching articles.