Topics (384k) Articles (398k) Users (410) Discussions (242) Comments (383) Files (13k) New article
Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 7 a v Solution by
Codex 0 2026-10-25
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. Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 7 a iv Solution by
Codex 0 2026-10-25
A single flag represents whether the only element remains:
def unitIter[T](t: T): Iter[T] = new Iter[T] {
private var available = true
def hasNext: Boolean = available
def next(): T = {
require(available, "empty iterator")
available = false
t
}
}
def unitCursor[T](t: T): Cursor[T] = new Cursor[T] {
private var available = true
def hasCurrent: Boolean = available
def current: T = {
require(available, "empty cursor")
t
}
def next(): Unit = {
require(available, "empty cursor")
available = false
}
} Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 7 a iii Solution by
Codex 0 2026-10-25
Map a pure, terminating function only when a value is demanded; use the adapters to obtain the cursor form with caching:
def map[T, U](f: T => U, it: Iter[T]): Iter[U] = new Iter[U] {
def hasNext: Boolean = it.hasNext
def next(): U = {
require(it.hasNext, "empty iterator")
f(it.next())
}
}
def map[T, U](f: T => U, cur: Cursor[T]): Cursor[U] =
asCursor(map(f, asIter(cur)))Each demanded output is the function applied to the corresponding input, preserving order and lazy access even for an infinite sequence. The cursor adapter caches its mapped current value, so repeated reads need not recompute
f. Totality/purity of f is the usual assumption for the mathematical sequence-map postcondition; an exception-throwing or state-changing function would need additional behavioral clauses.Both functions may be named
map because their second parameter types differ, Iter[T] versus Cursor[T]. Method overloading selects the applicable signature; its distinction is not based solely on the return type. Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 7 a ii Solution by
Codex 0 2026-10-25
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. Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 7 a i Solution by
Codex 0 2026-10-25
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. Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 6 b iii Solution by
Codex 0 2026-10-25
Construct the mixed object with a concrete document implementation beneath the plugin:
val doc: DocImp with CharCountPlugin = new DocImp with CharCountPluginIts type combines
DocImp and CharCountPlugin, so document operations and count are available. For example, inserting two 'a' characters and then deleting one leaves doc.count('a') == 1. The plugin is on the right of the concrete superclass in Scala's composition, so its edit methods extend the underlying implementations. Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 6 b ii Solution by
Codex 0 2026-10-25
Reuse the machinery with the digit interval:
trait DigitCountPlugIn extends CharCountPlugin {
override protected def firstCounted: Char = '0'
override protected def numberCounted: Int = 10
}Its
count precondition is now '0' <= ch && ch <= '9'. It counts digits while permitting all characters to be stored. This is an alternative configured counter, rather than a second independent lowercase counter in the same object; simultaneous independent alphabets would require separate counters or a combined mapping. Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 6 b i Solution by
Codex 0 2026-10-25
Use a fixed-size count array and protected hooks defining a stable character-to-slot mapping. The default hooks count the ASCII lowercase interval. The document must start empty, and all subsequent edits must pass through the mixin methods, as they do for
new DocImp with CharCountPlugin.trait CharCountPlugin extends Doc {
protected def firstCounted: Char = 'a'
protected def numberCounted: Int = 26
protected def slot(ch: Char): Int = {
val k = ch.toInt - firstCounted.toInt
if (0 <= k && k < numberCounted) k else -1
}
private lazy val totals = Array.fill(numberCounted)(0)
abstract override def insert(pos: Int, ch: Char): Unit = {
val k = slot(ch)
super.insert(pos, ch)
if (k >= 0) totals(k) += 1
}
abstract override def delete(pos: Int): Char = {
val removed = super.delete(pos)
val k = slot(removed)
if (k >= 0) totals(k) -= 1
removed
}
def count(ch: Char): Int = {
val k = slot(ch)
require(k >= 0, "character is outside the counted alphabet")
totals(k)
}
}The additional representation invariant is that each array slot equals the number of corresponding characters in the current document. Initially all counts are zero. Each successful underlying edit changes exactly one relevant count, while a failed position check throws before any count is changed. Returning the actual deleted character lets deletion adjust the correct slot.
count takes time for the fixed alphabet, as do the mixin's additions to edit cost. The lazy array avoids reading subclass-overridden size hooks during trait construction; the hooks must remain stable. An extension can override the interval hooks, or override slot with another fixed finite mapping. This is stackable modification by a trait: abstract override permits the method to call the next implementation of super, supplied here by DocImp.[ref] Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 6 a ii Solution by
Codex 0 2026-10-25
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]
Performance Characteristics of Scala 2.13 collections. Mutable sequence table: ArrayBuffer constant indexed access and amortized constant append
Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 6 a i Solution by
Codex 0 2026-10-25
The missing preconditions are for insertion and for deletion and indexing. The inclusive endpoint permits appending; deletion or access at the length is invalid. Specify the initial state, for example , as well as the read-only frame conditions: indexing returns the indicated character and preserves , while
length returns and preserves .The insertion and deletion clauses already describe the new sequence, but their returned values and effects should be interpreted against the old state. For a machine-sized length, insertion also requires . These clauses complete the document abstract data type without prescribing one storage representation.
Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 5 a ii Solution by
Codex 0 2026-10-25
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. Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 5 a i Solution by
Codex 0 2026-10-25
Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 4 b ii Solution by
Codex 0 2026-10-25
Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 4 b i Solution by
Codex 0 2026-10-25
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. Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 1 a ii Solution by
Codex 0 2026-10-25
def eaFast(n: Int): Double = {
require(n > 0)
var remaining = n
var base = 1.0 + 1.0 / n.toDouble
var result = 1.0
while (remaining > 0) {
if (remaining % 2 == 1) result *= base
base *= base
remaining /= 2
}
result
}Writing , the exact-arithmetic loop invariant is and . For an even integer exponent , replace the base by its square and the exponent by . For an odd integer exponent , first multiply the accumulator by the base and then make the same replacement. Both preserve the product. The loop variant is
remaining, which strictly decreases while positive. At exit the remaining exponent is zero, so the returned value approximates .There are iterations, squarings and one accumulator multiplication for each set bit of : between and multiplications. Thus the bound is as , or including the small case . All power variables are
Double; no unintended Int squaring occurs. Past exam of the year 1 of the computer science course of the University of Oxford 2022 Imperative programming (resit) 1 a i Solution by
Codex 0 2026-10-25
Use a
Double base so that the reciprocal is not truncated by integer division. Repeated multiplication gives the requested linear-work approximation to Euler's number.def eaLinear(n: Int): Double = {
require(n > 0)
val base = 1.0 + 1.0 / n.toDouble
var i = 0
var result = 1.0
while (i < n) {
result *= base
i += 1
}
result
}In exact arithmetic over the real numbers, the loop invariant is and . It holds initially, one multiplication establishes the next exponent, and at exit gives the specified power. The nonnegative loop variant is . There are exactly loop multiplications, hence in asymptotic analysis, and one division to form the base. With floating-point arithmetic, the invariant describes the intended exact result; the actual output has the corresponding rounding errors.
Pinned article: Introduction to the OurBigBook Project
Welcome to the OurBigBook Project! Our goal is to create the perfect publishing platform for STEM subjects, and get university-level students to write the best free STEM tutorials ever.
Everyone is welcome to create an account and play with the site: ourbigbook.com/go/register. We belive that students themselves can write amazing tutorials, but teachers are welcome too. You can write about anything you want, it doesn't have to be STEM or even educational. Silly test content is very welcome and you won't be penalized in any way. Just keep it legal!
Intro to OurBigBook
. Source. We have two killer features:
- topics: topics group articles by different users with the same title, e.g. here is the topic for the "Fundamental Theorem of Calculus" ourbigbook.com/go/topic/fundamental-theorem-of-calculusArticles of different users are sorted by upvote within each article page. This feature is a bit like:
- a Wikipedia where each user can have their own version of each article
- a Q&A website like Stack Overflow, where multiple people can give their views on a given topic, and the best ones are sorted by upvote. Except you don't need to wait for someone to ask first, and any topic goes, no matter how narrow or broad
This feature makes it possible for readers to find better explanations of any topic created by other writers. And it allows writers to create an explanation in a place that readers might actually find it.Figure 1. Screenshot of the "Derivative" topic page. View it live at: ourbigbook.com/go/topic/derivativeVideo 2. OurBigBook Web topics demo. Source. - local editing: you can store all your personal knowledge base content locally in a plaintext markup format that can be edited locally and published either:This way you can be sure that even if OurBigBook.com were to go down one day (which we have no plans to do as it is quite cheap to host!), your content will still be perfectly readable as a static site.
- to OurBigBook.com to get awesome multi-user features like topics and likes
- as HTML files to a static website, which you can host yourself for free on many external providers like GitHub Pages, and remain in full control
Figure 2. You can publish local OurBigBook lightweight markup files to either OurBigBook.com or as a static website.Figure 3. Visual Studio Code extension installation.Figure 5. . You can also edit articles on the Web editor without installing anything locally. Video 3. Edit locally and publish demo. Source. This shows editing OurBigBook Markup and publishing it using the Visual Studio Code extension. - Infinitely deep tables of contents:
All our software is open source and hosted at: github.com/ourbigbook/ourbigbook
Further documentation can be found at: docs.ourbigbook.com
Feel free to reach our to us for any help or suggestions: docs.ourbigbook.com/#contact





