Demand-driven filtering iterator Created 2026-10-24 Updated 2026-10-31
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. Index-function representation of a list Created 2026-10-24 Updated 2026-10-31
Represent a finite list by a function from indices to optional values. There must be a length for which exactly the indices are present; later indices are absent, so the representation has no holes. Prepending and removing a head shift indices, taking restricts the present domain, and dropping translates it. Functional composition can accumulate query costs, so array-like constant-time access does not follow from the representation type.
Use the representation invariant that each input occurs at most once in
data. It is an immutable finite partial function, so extending or removing an input returns a new object. The source comment get(x) returns Some(x) contradicts both the type signature Option[U] and the input-output semantics: it must return Some(y) when .class PartialFn[T,U](val data: List[(T,U)]) {
require(data.map(_._1).distinct.size == data.size)
def get(x: T): Option[U] =
data.find(p => p._1 == x).map(_._2)
def add(x: T, y: U): PartialFn[T,U] =
new PartialFn[T,U]((x,y) :: data.filterNot(p => p._1 == x))
def remove(x: T): PartialFn[T,U] =
new PartialFn[T,U](data.filterNot(p => p._1 == x))
def compose[V](that: PartialFn[U,V]): PartialFn[T,V] = {
val pairs: List[(T,V)] = data.flatMap { case (x,y) =>
that.get(y).toList.map(z => (x,z))
}
new PartialFn[T,V](pairs)
}
}get returns an optional value rather than inventing a result outside the domain of a function. add removes any old pair for its input before inserting the replacement; remove retains exactly the other pairs. In compose, undefined intermediate outputs contribute an empty list; defined outputs contribute one pair. Hence uniqueness of input keys is preserved.The printed composition order is deliberately followed:This is the stated function composition, even though some libraries use the name
compose for the opposite order.Use pattern matching and an optional value to distinguish absence from a present result:The representation precondition is that equal keys always have equal associated values, with a stable equality relation on keys. This permits harmless repeated identical pairs; it excludes conflicting outputs for one input.
class PartialFn[T, U](val data: List[(T, U)]) {
def get(x: T): Option[U] =
data.find { case (key, _) => key == x }.map(_._2)
def remove(x: T): PartialFn[T, U] =
new PartialFn[T, U](data.filterNot { case (key, _) => key == x })
def add(x: T, y: U): PartialFn[T, U] =
new PartialFn[T, U]((x, y) ::
data.filterNot { case (key, _) => key == x })
}get therefore returns Some(y) exactly when the represented partial function is defined at x. Removing filters out every occurrence of the key and leaves all other pairs unchanged. Adding first removes every old occurrence and prepends exactly the new pair, proving the partial function update postcondition even if the input contains duplicate identical pairs. The immutable linked list representation leaves the original mapping unchanged. Each operation has worst-case time complexity for pairs under constant-cost key equality.A function returning an optional value supplies the representation:For a finite represented list, require a length such that every index returns
newtype List a = List (Int -> Maybe a)Just its element and every other index returns Nothing. Thus there are no holes: absence at a nonnegative index implies absence at every later index. The type alone does not enforce this invariant, or algorithm termination of a query. An infinite represented sequence may instead have a present value at every nonnegative index; operations requiring its length will not terminate.The implementations below return
Nothing at negative indices. Finite lengths, requested counts and intermediate indices are assumed to fit Int; explicit guards on index shifts also prevent arithmetic wraparound from exposing an invalid element.