Cursor 2026-10-24
Demand-driven filtering iterator 2026-10-24
An iterator adapter that consumes underlying values only far enough to find the next value passing a predicate. Store at most one passing value in an optional value. With no cached value, its abstract remainder is the filtered underlying remainder; with cached , it is followed by that filtered remainder.
Construction performs no read. On
hasNext or next, consume rejected values until one passes or the input is exhausted. Cache the passing value immediately and stop. Repeated availability tests reuse it. Returning it clears the cache without prefetching another value. This gives a precise demand boundary even when producing source values is expensive. Availability need not terminate on an infinite input with no further passing value. Iterator concatenation 2026-10-24
Consume the first iterator until it is exhausted, then consume the second. Short-circuit availability testing avoids polling the second producer while the first still has entries. Construction need not request any entry. If both wrappers share an underlying cursor, however, draining the first may also consume entries the second would otherwise have returned.
Iterator pattern 2026-10-25
Expose sequential consumption through availability and next-element operations while hiding storage and traversal details. The abstraction supports common clients for arrays, linked structures or generated sequences. A separate iterator can own separate cursor state; an alias of the same iterator does not create an independent traversal.
Lazy iterator-to-cursor adapter 2026-10-24
Represent a cursor by an underlying consuming iterator plus an optional cached head. Without a cache, its abstract sequence is the iterator's sequence; with cached , it is concatenated with the remaining iterator sequence.
current fetches and caches exactly one value when needed, repeated reads reuse it, and advance discards it. Availability testing need not fetch a value, and construction need not query the input. This avoids forcing a future stream element that the client may never request.The Iterator pattern gives clients a uniform way to inspect and consume the elements of a collection without exposing how that collection is stored. An iterator has a traversal state separate from the abstract collection interface, so the same client loop can process an array, a linked list or generated values. It preserves encapsulation and representation independence: changing the underlying data structure need not change the traversal client. Separate iterator instances can track separate traversals; two aliases of one consuming iterator instead share its progress.
The essential abstract methods are:
def hasNext: Boolean
def next(): ThasNext reports whether another result is available. next() returns that result and advances; if exhausted, it throws NoSuchElementException. The other ordinary iterator operations are implemented using these primitives. An availability check must not silently discard a result visible to the client, although an adapter may need to fetch and cache underlying values in order to decide availability.[ref]Scala 2.12 iterator API documentation. Iterator abstract members hasNext and next; exhaustion contract
The plus marks a covariant type parameter: if , then
Iterator[S] <: Iterator[T]. An iterator producing a subtype's values can be used where an iterator of the supertype is required. For example, a string iterator can be viewed as an iterator of AnyRef, because strings are reference values. The direction is appropriate for a producer: calling next() gives a value valid at the wider type. It would not be safe to expose an ordinary insertion method accepting arbitrary wider-type values into a subtype-only collection. This is a subtyping relationship, not a request to transform each produced value.[ref]Scala 2.12 types specification. Conformance of parameterized types with covariant parameters
Use iterator concatenation and short-circuit the second availability test while the first iterator still has results:The abstract remaining sequence is the remaining sequence of
class ConcatIterator[T](first: Iterator[T], second: Iterator[T])
extends Iterator[T] {
def hasNext: Boolean = first.hasNext || second.hasNext
def next(): T = {
if (first.hasNext) first.next()
else second.next()
}
}first followed by that of second. Each next() consumes the head of precisely this sequence. When both are exhausted, delegation to second.next() raises the required exception. Construction consumes no elements, and hasNext never calls either underlying next(). As usual, clients should not separately consume these iterators while relying on the adapter's traversal. Sharing one underlying iterator between both arguments also shares its state; it does not duplicate its elements.The expression constructs a valid iterator object; it does not itself return the concatenated numeric or character values. The element types have a common supertype Successive calls return
AnyVal, and covariance lets Iterator[Int] and Iterator[Char] serve as the two inputs to ConcatIterator[AnyVal]. In the ordinary Scala 2 typing of this expression, the common element type is AnyVal. One can make the intended widening explicit:new ConcatIterator[AnyVal](
List(1, 2, 3).iterator,
List('a', 'b', 'c').iterator
)1, 2, 3, 'a', 'b', 'c', then exhaustion. Characters remain characters: viewing an existing iterator at a wider type does not convert its contents to integer character codes. No next() is invoked merely by constructing this adapter. The common-type argument uses subtype polymorphism and covariant type parameters, rather than requiring the two concrete input types to be equal.Make two separate character iterators so that scanning uppercase letters does not consume the lowercase pass:Each filtering iterator preserves the original order of its selected characters. Concatenation therefore yields all uppercase letters in source order, then all lowercase letters in source order, omitting other characters. The two
def upperLower(s: String): Iterator[Char] = {
val upper = new FilterIterator[Char]((c: Char) => c.isUpper, s.iterator)
val lower = new FilterIterator[Char]((c: Char) => c.isLower, s.iterator)
new ConcatIterator[Char](upper, lower)
}s.iterator calls are essential: reusing a single consuming iterator would let the first pass discard lowercase characters before the second pass could return them. Construction remains lazy; an empty string yields an exhausted iterator without a special case.The iterator pattern exposes sequential traversal through a small interface while hiding the representation of the underlying data. A client asks whether another entry is available and requests the next entry; it need not know whether the producer uses an array, linked list, tree or generated sequence.
The key design benefit is encapsulation of traversal state and representation. The same consuming algorithm can work through the interface for different producers, and each traversal can keep its own cursor state when a fresh iterator is supplied. This independence requires separate consumption states; two references to one iterator share a cursor.
The two abstract methods areThe first reports availability; the second returns and consumes the next entry, or throws a
def hasNext: Boolean
def next(): TNoSuchElementException if none exists. Other standard iterator operations are supplied in terms of this interface. Repeated availability checks must not lose entries, although they may perform preparatory internal work.[ref]Scala 2.13 iterator API documentation. Iterator, abstract members hasNext and next; class-level mutation and reuse contract
Yes in special cases; an ordinary finite iterator that progresses through its input needs changing consumption state. For a genuine immutable counterexample, an iterator over an infinite constant sequence needs no changing fields:Removing the first entry of that sequence leaves the same sequence, so this satisfies the iterator behaviour without changing object state. A permanently empty iterator is another example.
final class Ones extends Iterator[Int] {
def hasNext: Boolean = true
def next(): Int = 1
}For a normal finite nonempty traversal, however, repeated
next() calls must eventually change the returned entry or availability, so the implementation needs mutable cursor state somewhere. Declaring only val fields is not a proof of deep immutability: those fields can refer to another mutable iterator, an array, or a stateful closure. The standard API's description of iterators as mutable captures the usual consuming case, rather than enforcing a mutable field in every possible implementation.Delay both initial skipping and every later skipped entry until a caller actually requests availability or an output:The state flag means that the next underlying entry, if present, must be skipped before the next selected entry can be exposed. It is initially false for odd positions and true for even positions. Returning a selected entry reinstates that obligation.
class HalfIterator[T](iter: Iterator[T], odd: Boolean)
extends Iterator[T] {
private var needSkip = !odd
def hasNext: Boolean = {
if (needSkip && iter.hasNext) {
iter.next()
needSkip = false
}
iter.hasNext
}
def next(): T = {
if (!hasNext)
throw new java.util.NoSuchElementException("empty half iterator")
val result = iter.next()
needSkip = true
result
}
}Construction does not call the underlying iterator at all. A successful availability check may discard the one required intervening entry, but does not fetch the selected entry. It then clears the flag, so repeated availability checks cannot skip again. To decide whether a second entry exists through this interface, consuming the preceding discarded entry is necessary. After
next() returns, it does not eagerly request the following skipped entry. This gives the intended laziness while retaining correct exhaustion behaviour, including an even-position iterator over a singleton input.Use short-circuit evaluation so that even an availability test of the second iterator is delayed until the first is exhausted:Each successful call returns from the first producer while it has entries, then from the second. There is no constructor-time request or element prefetch. This implements concatenation of the producers' remaining sequences, subject to their ordinary iterator contracts.
class ConcatIterator[T](first: Iterator[T], second: Iterator[T])
extends Iterator[T] {
def hasNext: Boolean = first.hasNext || second.hasNext
def next(): T = {
if (first.hasNext) first.next()
else if (second.hasNext) second.next()
else throw new java.util.NoSuchElementException("empty concatenation")
}
}For the implementations above the value isThe first half-iterator returns the odd-position characters while consuming the even-position characters as its required skips. By the time it is exhausted, it has consumed the entire shared underlying iterator. The second half-iterator consequently has no entries left when concatenation reaches it. Sharing one source does not create two independent traversals. Neither constructor consumes anything, so it also does not shift the first output before iteration begins. The source's implementation references here should be read as parts (d) and (e); the printed reference to (c) and (d) is a cross-reference slip.
List('1', '3', '5', '7', '9')Programming language polymorphism lets a common interface or operation apply to values of more than one type. In subtype polymorphism, a variable typed by a base class or trait can refer to instances of different implementing classes. A method call is checked through that common interface, then dynamic dispatch selects the receiver's overriding implementation. For example, a consumer using
Iterator[Int] can accept either a filtering iterator or a concatenating iterator without knowing its concrete class.Inheritance can establish a subtype relationship, while method overriding provides distinct implementations of a shared method signature. The classes must still honour the common behavioural contract; sharing a name alone does not establish substitutability. Parametric polymorphism is different: the type parameter in
Iterator[T] or FiniteDist[T] lets a definition work uniformly over element types. Method overloading is a further form of software polymorphism in which the compiler selects a signature using the static argument types; it should not be confused with runtime receiver dispatch.Worked solutions for the held Oxford Long Vacation examination of 6 September 2022. Source release: released examination PDF. The cover date controls the identity despite the archive's older directory name. All eight questions and all 44 marked leaves are addressed, including alternatives not required of one candidate. No question statements or source figures are reproduced.
Code uses Scala 2 syntax. Mathematical invariants distinguish exact arithmetic from floating-point execution, and fixed-width sizes and counts are checked where necessary. Mutable structures are assumed to be used sequentially, with their stated representation invariants; destructive iterator adapters own their input states exclusively. Each question has its own code context, so the sparse-vector
Node and tree Node[T] are independent definitions. No source attachment is missing.def asIter[T](cur: Cursor[T]): Iter[T] = new Iter[T] {
def hasNext: Boolean = cur.hasCurrent
def next(): T = {
require(cur.hasCurrent, "empty iterator")
val answer = cur.current
cur.next()
answer
}
}The two abstract sequences are identical. A
next call returns the old head and removes it, while hasNext only queries availability. No element is read at construction or during an availability test, and no later element is read while returning the current one. Thus the adapter is not too eager. The source's Cursor.current clause needs the implicit precondition that its sequence is nonempty; taking the head of an empty sequence is undefined. The checks above satisfy that requirement even without relying on the client convention.