= Solution
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>.
Back to article page