Let the abstract state be the set of occupied coordinate pairs. For the Boolean array, the abstraction function is . Its representation invariant requires an Boolean array and the downward-closure property
Thus is an order ideal for the coordinatewise partial order. Removing an upper-right rectangle preserves this property. The empty set is allowed after the losing move.
For the row-boundary array, the abstraction function and representation invariant are
The array has length . Removing at replaces each row(j) for by . Earlier rows are unchanged. This both implements the abstract removal and preserves the nonincreasing boundary.
Without a cached count, the Boolean representation takes for Count, for Legal, worst-case for Move, and for a full-grid Display. The boundary representation takes for Count, for Legal, worst-case for Move, and for the same full-grid Display. Their space complexities are respectively and . A scalar cached total makes Count in either representation if each move updates it consistently. The implementation below uses that cache with the boundary array, so it achieves constant-time Count and Legal, linear worst-case Move, and linear storage. Full-grid output still takes quadratic time because it emits quadratic many cell positions.
A fixed-capacity implementation needs a bounded contract: , and a push requires strict inequality. Pop preconditions and all successful-operation postconditions remain unchanged. An implementation that rejects full-array pushes cannot satisfy an unbounded specification promising every push; the capacity restriction must be part of the agreed abstract data type, or its resource-failure behavior must be specified.
Use one array and two boundaries left and right. The first stack occupies indices below left and grows rightwards; the second occupies indices at least right and grows leftwards. The representation invariant is
The abstraction function is
Both lists are top-first. The unoccupied middle interval has length right - left; a push is possible exactly when this is positive. Thus every array position can be used by either stack, avoiding a rigid half-array allocation.
Figure 1.
Original shared-array double-stack layout: two stacks grow towards a common free interval
. Details.
The implementation maintains the two boundaries from part b:
class ArrayDoubleStack(val MAX: Int) extends DoubleStack {
  require(MAX >= 0)
  private val data = new Array[Int](MAX)
  private var left = 0
  private var right = MAX

  def isEmptya: Boolean = left == 0
  def isEmptyb: Boolean = right == MAX

  def pusha(x: Int): Unit = {
    require(left < right, "double stack is full")
    data(left) = x
    left += 1
  }
  def pushb(x: Int): Unit = {
    require(left < right, "double stack is full")
    right -= 1
    data(right) = x
  }
  def popa: Int = {
    require(left > 0, "stack a is empty")
    left -= 1
    data(left)
  }
  def popb: Int = {
    require(right < MAX, "stack b is empty")
    val answer = data(right)
    right += 1
    answer
  }
}
Initialization gives empty abstract lists. Each push consumes one free position next to the appropriate top; each pop returns that top and releases its position. The guards preserve the ordering of the two boundaries and prevent invalid indexing. The abstraction function shows directly that the untouched stack is unchanged. Keeping data private prevents clients from violating the representation invariant; this strengthens encapsulation relative to a publicly exposed implementation field without changing the six required operations.
All six operations run in time, with a fixed array of MAX integers and constant extra state. Creating and zero-initializing the array takes time, outside those operation bounds. Capacity zero gives a valid always-empty bounded structure whose pushes fail their precondition.
A sentinel node supplies a permanent predecessor for the first real node. Inserting or deleting at the front then uses the same predecessor.next update as an interior operation. Its datum is not a multiset element: the chosen extreme integer is merely unused sentinel data.
A representation invariant requires a nonnull fixed header, a finite acyclic chain starting at bagHeader.next, and exclusion of the header from that chain. Each node identity occurs once, although different nodes may have equal data. Representation links are owned by the implementation, so outside mutation cannot alter them unexpectedly.
The abstraction function counts occurrences:
This yields nonnegative multiplicities and finite support. Initialization with a null successor gives the empty multiset. Excluding the sentinel is essential even when a real item equals its datum.
Implement the count function directly in a private array:
class ArrayIntBag(val MAX: Int) extends IntBag {
  require(MAX > 0)
  private val c = new Array[Int](MAX)
  private def inRange(x: Int): Boolean = 0 <= x && x < MAX

  def add(x: Int): Unit = {
    require(inRange(x))
    require(c(x) < Int.MaxValue)
    c(x) += 1
  }
  def remove(x: Int): Unit = {
    require(inRange(x))
    require(c(x) > 0)
    c(x) -= 1
  }
  def count(x: Int): Int = {
    require(inRange(x))
    c(x)
  }
}
The representation invariant is that c has length MAX and every stored count is nonnegative. The abstraction function gives in range and zero outside it. Zero initialization establishes the invariant. An allowed addition changes exactly one entry without overflow; an allowed removal changes exactly one positive entry without making it negative; querying changes none. Thus each method implements its abstract postcondition as well as preserving the representation invariant. The private array prevents clients from bypassing those guarantees.
Store the number of positive buckets, initially zero:
private var distinct = 0

def size: Int = distinct
Extend the representation invariant by
The abstraction function for the multiset is unchanged. The size operation has no precondition beyond a valid receiver; it returns the cardinality of the multiset support and changes no state. Replace the modifying methods inside the class by
def add(x: Int): Unit = {
  require(inRange(x))
  require(c(x) < Int.MaxValue)
  if (c(x) == 0) distinct += 1
  c(x) += 1
}

def remove(x: Int): Unit = {
  require(inRange(x))
  require(c(x) > 0)
  c(x) -= 1
  if (c(x) == 0) distinct -= 1
}
Only transitions from zero to one or from one to zero change the support. Additions to an already positive bucket and removals leaving it positive preserve distinct. Thus size takes time, independently of MAX, and the other operations retain constant-time array access. Initialization still takes time.
Keep private references first and last, both initially null. A nonempty representation is a finite acyclic chain from first through next to last. Its representation invariant requires first.prev == null, last.next == null, and reciprocal links between every adjacent pair. The nodes reached by following prev from last are exactly the same nodes in reverse order. No internal node reference is exposed to clients.
The abstraction function reads the datum values along the forward chain, giving the abstract sequence from left to right. The empty representation has both endpoint references null; the invariant requires that either both are null or both are nonnull. A single node has both links null. Updating both endpoint references when removing that last node is therefore essential.
Keep a head, a tail and a cached length:
private var first: QueueNode = null
private var last: QueueNode = null
private var size = 0
The representation invariant requires and a finite acyclic null-terminated linked list of exactly size nodes starting at first. When size == 0, both endpoints are null. Otherwise last is precisely the final reachable node and last.next == null.
The abstraction function maps the chain from first to last to its payload sequence. Initially all pointers are null and the cached size is zero, giving the empty abstract queue. A tail pointer makes append constant-time; the cached size makes the length query constant-time without traversing the chain.
Use the trait and node definition above. The reference to a “stack” in this part is a wording error: the declared class and operations implement a FIFO queue.
class ListQueue extends Queue[Int] {
  private var first: QueueNode = null
  private var last: QueueNode = null
  private var size = 0

  def enqueue(x: Int): Unit = {
    require(size < Int.MaxValue, "queue size limit")
    val fresh = new QueueNode(x, null)
    if (last == null) first = fresh
    else last.next = fresh
    last = fresh
    size += 1
  }

  def dequeue(): Int = {
    require(first != null, "empty queue")
    val answer = first.value
    first = first.next
    size -= 1
    if (first == null) last = null
    answer
  }

  def clear(): Unit = {
    first = null
    last = null
    size = 0
  }

  def isEmpty: Boolean = size == 0
  def length: Int = size
}
Appending links the fresh node after the old tail, or establishes both endpoints when empty. Removing the head preserves the tail unless the final node was removed, in which case both endpoints become null. Clearing only discards the endpoint references; it need not visit or overwrite unreachable nodes. Thus all operations are in the usual pointer-operation/allocation cost model, with later garbage collection excluded from an individual operation's bound. The representation invariant and abstraction function show the specified sequence changes, including empty/singleton transitions.
Within the sparse-vector implementation, use:
class Node(var value: Double, val index: Int,
           var prev: Node, var next: Node)
The header's index is the fixed vector dimension, so it need not change after construction. For a data node it is the coordinate index. The representation invariant requires a finite simple cycle through the header, reciprocal predecessor/successor links, strictly increasing indices in the forward data-node traversal, indices in , and a nonzero value at every data node. The header's value is ignored. The abstraction function assigns stored values to their indices and zero to all missing coordinates.
The numerical code below takes entries to be finite Double values. Excluding NaN and infinities makes the relationship to a real sparse vector explicit; arithmetic results still follow ordinary floating-point arithmetic.
For the flattened Boolean array, the abstraction function selects the true cells. A suitable representation invariant requires the occupied cells to form a order ideal: an occupied implies occupancy of every with . Deleting a northeast rectangle preserves that property.
For column heights, the invariant is , and the abstraction function is . A legal move replaces every for by , preserving the invariant. This is an order-ideal board representation.
For a fair comparison, allow either representation a scalar cached token count: Count and Legal then take in both. Without that cache, counting needs Boolean inspections versus height additions. A move takes in the worst case in the flattened representation and with heights. Displaying the full square takes character positions in either. Heights use array entries rather than , so they are superior for the requested operations; their initialization also takes only time.
A mutable character buffer supplies a cached length and constant-time indexed access:
class DocImp extends Doc {
  private val data = scala.collection.mutable.ArrayBuffer.empty[Char]

  def insert(pos: Int, ch: Char): Unit = {
    require(0 <= pos && pos <= data.length)
    require(data.length < Int.MaxValue)
    data.insert(pos, ch)
  }

  def delete(pos: Int): Char = {
    require(0 <= pos && pos < data.length)
    data.remove(pos)
  }

  def apply(pos: Int): Char = {
    require(0 <= pos && pos < data.length)
    data(pos)
  }

  def length: Int = data.length
}
The representation invariant is that data is a well-formed private dynamic array of at most Int.MaxValue characters. Its abstraction function is the buffer's character sequence. Initialization is empty; insertion and removal at the checked position implement exactly the required sequence edits, and the queries do not modify it.
length and indexing are because a dynamic array stores its used length and supports address arithmetic for indexed access. An insertion or deletion at position in a document of length shifts entries; insertion can additionally resize the backing storage, giving amortized time and worst-case for one operation. Deletion is with an additional constant term.
Building a document by appending the characters of a length- string costs amortized time, since geometric capacity growth copies a total of only entries. Thus this implementation does not have the quadratic append behavior of repeated immutable-string concatenation. A known final length can also be reserved in advance. The bounds use the usual amortized analysis of a dynamic array.[ref]
Cache only a requested current element. Merely constructing the cursor, or asking whether it is nonempty, must not fetch a value from the iterator:
def asCursor[T](it: Iter[T]): Cursor[T] = new Cursor[T] {
  private var cached: Option[T] = None

  def hasCurrent: Boolean = cached.nonEmpty || it.hasNext

  def current: T = {
    if (cached.isEmpty) {
      require(it.hasNext, "empty cursor")
      cached = Some(it.next())
    }
    cached.get
  }

  def next(): Unit = {
    if (cached.nonEmpty) cached = None
    else {
      require(it.hasNext, "empty cursor")
      it.next()
      ()
    }
  }
}
The abstraction function for the adapter is it.seq when no value is cached and [cachedValue] ++ it.seq otherwise. Fetching current removes one underlying iterator value but saves it, so the cursor's abstract sequence is unchanged; repeated reads return the same value. Advancing either discards that cache or consumes one uncached iterator value. No following element is prefetched.
A too-eager implementation could call it.next() at construction, or fetch a new element immediately after every advance, even when the client never requests it. That can trigger unnecessary work or nontermination on a difficult stream. This lazy iterator-to-cursor adapter avoids both behaviors and checks the underlying access preconditions itself. The wrapped iterator is assumed to be used exclusively through the adapter; independent external consumption would invalidate the shared-state abstraction.
Prefer the first sequence whenever it is nonempty, otherwise delegate to the second:
def cat[T](t: Iter[T], u: Iter[T]): Iter[T] = new Iter[T] {
  def hasNext: Boolean = t.hasNext || u.hasNext
  def next(): T = {
    if (t.hasNext) t.next()
    else {
      require(u.hasNext, "empty iterator")
      u.next()
    }
  }
}

def cat[T](t: Cursor[T], u: Cursor[T]): Cursor[T] = new Cursor[T] {
  def hasCurrent: Boolean = t.hasCurrent || u.hasCurrent
  def current: T = {
    if (t.hasCurrent) t.current
    else {
      require(u.hasCurrent, "empty cursor")
      u.current
    }
  }
  def next(): Unit = {
    if (t.hasCurrent) t.next()
    else {
      require(u.hasCurrent, "empty cursor")
      u.next()
    }
  }
}
Assume the two destructive inputs have noninterfering states; in particular, passing the same iterator twice does not create a replayable copy of its original sequence. Fresh independent iterators provide that behavior. Under this ownership condition, the abstraction function is always the concatenation t.seq ++ u.seq. Consumption from t removes the concatenation's head; once t is empty, consumption from u does so. Availability tests short-circuit, so an infinite first sequence never unnecessarily touches the second.
For finite lengths , under a client loop with one availability test and one advance per element, the iterator version directly invokes t.hasNext times, including the final false test. For a cursor client with one availability test, one current read and one advance per element, the corresponding count is . These count calls made by the shown adapters, not extra checks hidden inside arbitrary underlying methods. A client can repeat availability/current queries arbitrarily often, so no bound depending only on applies without such a client protocol.
Cache a permanent “first input exhausted” flag after the first negative test to avoid repeated queries to t while traversing u; this reduces the dependence to first-input queries under the same protocols. Availability caching can reduce additional repeated checks. The flag is sound because an iterator's consumed sequence does not spontaneously grow again.
A complete multiset is a finite-support signed count function. An absent key has count zero; negative counts are allowed. One simple default implementation uses a protected immutable association list with unique keys and nonzero values. Its default is empty, and optimized subclasses may override the public queries.
trait CompleteMultiSet[T] {
  // Representation contract: finite, immutable, unique keys, nonzero counts.
  protected def entries: List[(T, Int)] = Nil

  def count(t: T): Int =
    entries.find { case (key, _) => key == t }.map(_._2).getOrElse(0)

  lazy val present: Seq[T] = entries.map(_._1)

  private def combine(other: CompleteMultiSet[T],
                      op: (Int, Int) => Int): CompleteMultiSet[T] = {
    val keys = (present.toList ++ other.present.toList).distinct
    val resultRows = keys.flatMap { t =>
      val n = op(count(t), other.count(t))
      if (n == 0) Nil else List((t, n))
    }
    new CompleteMultiSet[T] {
      override protected val entries: List[(T, Int)] = resultRows
    }
  }

  def +(other: CompleteMultiSet[T]): CompleteMultiSet[T] =
    combine(other, (a, b) => java.lang.Math.addExact(a, b))

  def -(other: CompleteMultiSet[T]): CompleteMultiSet[T] =
    combine(other, (a, b) => java.lang.Math.subtractExact(a, b))

  def max(other: CompleteMultiSet[T]): CompleteMultiSet[T] =
    combine(other, (a, b) => math.max(a, b))

  def min(other: CompleteMultiSet[T]): CompleteMultiSet[T] =
    combine(other, (a, b) => math.min(a, b))

  def <=(other: CompleteMultiSet[T]): Boolean =
    (present.toList ++ other.present.toList).distinct.forall { t =>
      count(t) <= other.count(t)
    }

  override def toString: String =
    present.distinct.filter(t => count(t) != 0)
      .map(t => s"$t:${count(t)}").mkString("{", ", ", "}")
}
The representation invariant and abstraction function assign the association-list value at a stored key and zero elsewhere. Every operation preserves both operands and returns a fresh immutable value. The finite-support pointwise operations principle makes the support union sufficient because every operation sends to zero. It is necessary for signed counts: the empty value is not at most a value having count at one key, although the empty left support alone would give a vacuous true comparison. Likewise, pointwise minimum can retain a negative count appearing only in the other operand.
present == List() represents the all-zero, empty complete multiset. The list-backed default count takes time for a first-entry hit or an empty list, and in the worst case for a missing or final key, assuming constant-cost key equality. The default binary operations can consequently take quadratic time; they are deliberately simple defaults, not optimized implementations. The lazy present avoids an early call to a subclass's representation during initialization.
Counts use Int, so addition and subtraction explicitly throw ArithmeticException when the mathematical result is unrepresentable instead of silently wrapping. Equivalently, their mathematical precondition requires representable result counts. This machine-size restriction is separate from permitting negative counts.
Use an immutable hash map for the concrete value, omitting zero-count entries:
class CompleteMultiSetImp[T](initial: Map[T, Int]) extends CompleteMultiSet[T] {
  private val table = initial.filter { case (_, n) => n != 0 }
  override def count(t: T): Int = table.getOrElse(t, 0)
  override lazy val present: Seq[T] = table.keys.toVector
}

def cms[T](ts: T*): CompleteMultiSet[T] =
  new CompleteMultiSetImp[T](ts.distinct.map(t => (t, 1)).toMap)
The abstraction function is the map with implicit zero outside its keys. Immutable storage makes saved values independent of later operations, and the inherited defaults dispatch through the overridden count and present. A hash lookup has expected constant cost under ordinary hashing assumptions, with collision-dependent worst cases.
Repeated arguments still get count one, because the constructor first removes duplicates. All other elements have count zero, satisfying the intended stronger reading of the constructor specification. Treating ts as a bag and adding one for every repetition would implement a different operation.
Keep a private immutable hash-map value in a mutable variable. Each update computes a complete replacement from snapshots and commits it once; this handles self-aliasing and arithmetic failure without partially modifying the receiver.
class MultiSetVarImp[T] extends MultiSetVar[T] {
  private var table = Map.empty[T, Int]

  def count(t: T): Int = table.getOrElse(t, 0)
  def present: Seq[T] = table.keys.toVector

  def copy(other: CompleteMultiSet[T]): Unit = {
    val saved = other.present.distinct.iterator
      .map(t => (t, other.count(t))).filter(_._2 != 0).toMap
    table = saved
  }

  private def snapshot(other: MultiSetVar[T]): Map[T, Int] =
    other.present.distinct.iterator
      .map(t => (t, other.count(t))).filter(_._2 != 0).toMap

  private def update(other: MultiSetVar[T], op: (Int, Int) => Int): Unit = {
    val left = table
    val right = snapshot(other)
    val keys = left.keySet ++ right.keySet
    val builder = Map.newBuilder[T, Int]
    keys.foreach { t =>
      val n = op(left.getOrElse(t, 0), right.getOrElse(t, 0))
      if (n != 0) builder += ((t, n))
    }
    val replacement = builder.result()
    table = replacement
  }

  def +(other: MultiSetVar[T]): Unit =
    update(other, (a, b) => java.lang.Math.addExact(a, b))

  def -(other: MultiSetVar[T]): Unit =
    update(other, (a, b) => java.lang.Math.subtractExact(a, b))

  def max(other: MultiSetVar[T]): Unit =
    update(other, (a, b) => math.max(a, b))

  def min(other: MultiSetVar[T]): Unit =
    update(other, (a, b) => math.min(a, b))

  def <=(other: CompleteMultiSet[T]): Boolean = {
    val left = table
    (left.keySet ++ other.present).forall { t =>
      left.getOrElse(t, 0) <= other.count(t)
    }
  }
}
The representation invariant is a finite map of nonzero counts with no representation exposed to callers. The abstraction function adds implicit zero elsewhere. getOrElse implements that default; keys enumerates stored keys; distinct removes repeated input support entries; toMap builds an independent immutable map; and the builder accumulates the next representation before it is assigned. The private immutable snapshots remain stable while the new state is computed.
For self-addition, both snapshots contain the same old counts, so they double; self-subtraction yields empty support; self-max and self-min leave the value unchanged. A distinct operand is never assigned or mutated. An overflow exception during building occurs before the final assignment, so the receiver remains unchanged. These are instances of snapshot-before-update semantics.
Lookup is expected under standard hash assumptions. Enumerating present takes for support size . Combining two instances of this implementation takes expected time and temporary storage, including snapshot construction. For an arbitrary argument implementation, add the costs of its count calls: copying a default list-backed complete multiset can require , whereas copying the hash-backed version is expected linear. No generic interface can guarantee another implementation's query cost. Inputs are assumed to have finite, complete support enumerations and stable counts during an operation; concurrent mutation would need a separate synchronization contract.
For each table index , let be the set of non-null nodes reachable by following next. A sufficient representation invariant is:
  • The array has exactly buckets, and every bucket heads a finite acyclic linked list.
  • The sets are pairwise disjoint, so one physical node cannot belong to two buckets.
  • Every reachable node has a valid user identity, lies in bucket hash(node.user), and has both counters between zero and Int.MaxValue.
  • User identities are unique among all reachable nodes, not merely within a single chain.
The abstraction function maps these nodes to the total functions of part (a): if the unique node for exists, and are its fields; otherwise both are zero. A stored record with two zero counters is harmless and has the same observable meaning as an absent record. This intentionally permits multiple concrete representations of the same abstract program state.
Initially every bucket is null, so the representation invariant holds and the abstraction function yields all zeros. The successful-update branch preserves every structural condition; the insertion branch creates a fresh node with a previously absent identity in its correct bucket. Both branches realize exactly the incoming-increment postcondition, demonstrating correct operations through an abstraction function.
Let denote the non-null nodes reachable from the head. The representation invariant requires a finite acyclic null-terminated linked list and pairwise distinct names among its reachable nodes. Names and numbers retain the interface's string data type interpretation; no ordering of names is required.
The abstraction function is the finite partial function
Name uniqueness makes this a function, rather than a relation assigning multiple numbers to one name. Initially list == null, so and the abstract book is empty. Unreachable allocated nodes are outside this abstract state.
Inside LinkedListBook, traverse without modifying the head or any node:
def printBook(): Unit = {
  var p = list
  while (p != null) {
    println(p.name + ":" + p.number)
    p = p.next
  }
}
The loop invariant is that precisely the prefix before p has been printed, once per node, in list order; p heads the remaining suffix. The null-terminated linked list invariant proves termination and prevents duplicate output through cycles. An empty list prints nothing. The abstraction function is unchanged, and the traversal takes node visits for entries.