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 2013 Imperative programming 7 b Solution by
Codex 0 2026-10-31
Ignoring predefined ancestors as permitted, the Scala trait linearizations areA
super call in a trait continues to the next applicable implementation in the receiver's Scala trait linearization. It is not permanently bound to the parent named when that trait was declared.For The shared ancestor
d1.h, enter B2.h. Its super.g reaches B1.g, and that method's super.f reaches A.f. For d2.h, enter B1.h; its super.g reaches B2.g, whose super.f again reaches A.f. Therefore the two output lines areh in B2, g in B1, f in A
h in B1, g in B2, f in AA occurs once in each Scala trait linearization, after the two specialized traits. This accounts for the final f in A in both lines. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 7 a Solution by
Codex 0 2026-10-31
An ordinary call such as the unqualified HenceThese arrows describe the method bodies entered in order, not the class inheritance relation. An inherited method continues to use the dynamic receiver for ordinary calls; it does not acquire the defining software class as a new receiver.
f inside A.g uses dynamic dispatch on the receiver. Thus c.g enters A.g, but its call to f reaches C.f. In contrast, B.h explicitly invokes super.f, which continues after B and therefore reaches A.f. Finally A.k dynamically dispatches its call to h, reaching B.h again. The output isg in A, f in C
h in B, f in A
k in A, h in B, f in A Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 6 d Solution by
Codex 0 2026-10-31
Keep the neighboring Fibonacci number in a private closure. Before calling the transition with , let its captured The transition returns and stores , preserving the paired Fibonacci recurrence. Initialization gives and , soUsing
following equal .def fibonacci(): Iter[BigInt] = {
var following = BigInt(1)
val step: BigInt => BigInt = (x: BigInt) => {
val result = following
following = x + following
result
}
new FunIter[BigInt](BigInt(0), step)
}BigInt allows all Fibonacci numbers rather than a finite prefix before integer overflow. Replacing BigInt by Int gives the source's suggested machine-integer interface with that limitation. With the FunIter implementation from part (c), each transition runs once per generated cell, and copied iterators can advance independently while sharing the privately owned generator. A simpler alternative uses a pure function on pairs, , and projects the first component; the closure above follows the source's suggested single-value approach directly. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 6 c Solution by
Codex 0 2026-10-31
A simple current-value implementation works when is a pure function, but sharing a mutable closure between copies would violate the independence requirement used in part (d). Use a memoized successor chain for stateful iterator copies: the generated sequence has shared cells, and each iterator has its own current-cell pointer.The representation invariant is that
object FunIter {
final class Cell[T](val head: T, f: T => T) {
lazy val tail: Cell[T] = new Cell[T](f(head), f)
}
}
class FunIter[T](v: T, f: T => T) extends Iter[T] {
private var cursor = new FunIter.Cell[T](v, f)
def hasNext: Boolean = true
def next(): T = {
val x = cursor.head
cursor = cursor.tail
x
}
def copy(): Iter[T] = {
val result = new FunIter[T](cursor.head, f)
result.cursor = cursor
result
}
}cursor points to the next cell to be returned, and each cell's lazy tail is computed at most once. The abstraction function is the infinite sequence of heads starting at that cell. Advancing changes only this iterator's pointer. A copy starts at the same cell, so both observe the same remaining heads even when their calls are interleaved:For a stateful , the generator state must be privately owned by this shared chain; unrelated external calls to must not change it. As usual, the values and transition must allow successive results to be observed without externally mutating old results. No interface can independently clone the hidden state of an arbitrary externally mutated closure from its function type alone. The chain avoids needing such cloning: a lagging copy reuses the already generated future instead of invoking again. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 6 b Solution by
Codex 0 2026-10-31
These operations consume the receiver, as expected for this destructive iterator interface. For
exists stops immediately after the first successful test.trait RichIter[T] extends Iter[T] {
def foreach(f: T => Unit): Unit = {
while (hasNext) f(next())
}
def exists(p: T => Boolean): Boolean = {
while (hasNext) {
if (p(next())) return true
}
false
}
def foldLeft[U](x: U)(f: (U, T) => U): U = {
var acc = x
while (hasNext) acc = f(acc, next())
acc
}
}foreach, after iterations the supplied function has been applied in order to exactly the first entries. For exists, every consumed entry has failed the predicate unless the method is returning true. For the left fold, the loop invariant after consuming entries isEach iteration extends the expression by the next entry on the right. For a finite remaining sequence, its unconsumed length is a common loop variant. ThusAn empty iterator invokes no callback, gives false for exists, and returns from the left fold. On an infinite iterator, exists can terminate when it finds a match; the other two operations ordinarily do not terminate. With constant-cost callbacks, finite input of length takes time complexity and constant auxiliary space. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 6 a Solution by
Codex 0 2026-10-31
A prefix-dropping iterator must preserve the first satisfying element. Reading it to test the predicate physically consumes it from the source, so keep it in a one-element buffer. Once a match is found, return the entire suffix without testing subsequent elements again.Assume the source iterator is owned by the wrapper, its
trait Iter[T] {
def hasNext: Boolean
def next(): T
def copy(): Iter[T]
}
class DropUntil[T](p: T => Boolean, iter: Iter[T]) extends Iter[T] {
private var seeking = true
private var saved: Option[T] = None
private def prepare(): Unit = {
if (seeking) {
while (saved.isEmpty && iter.hasNext) {
val x = iter.next()
if (p(x)) saved = Some(x)
}
seeking = false
}
}
def hasNext: Boolean = {
prepare()
saved.nonEmpty || iter.hasNext
}
def next(): T = {
prepare()
saved match {
case Some(x) => saved = None; x
case None =>
if (!iter.hasNext) throw new NoSuchElementException("next")
iter.next()
}
}
def copy(): Iter[T] = {
val result = new DropUntil[T](p, iter.copy())
result.seeking = seeking
result.saved = saved
result
}
}copy meets the stated independent-cursor contract, and is a pure function. Before preparation, the logical remainder is the source remainder with its maximal initial failing prefix removed. After preparation, it is the buffered match followed by the source remainder, or simply the source remainder when the buffer is empty. This describes the abstraction function and proves that repeated hasNext calls neither lose the match nor skip more values.Copying preserves the phase flag and optional buffered value and independently copies the source iterator. It does not rerun the predicate on an already accepted match. A shallow copy of the wrapper alone would give shared iterator consumption, violating the specification. ThereforeWhen no element satisfies , a finite source yields an empty iterator. Seeking can diverge on an infinite source with no match; this is inherent in determining whether its required suffix begins anywhere. An element object is not deep-copied by this cursor operation.
Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 5 c Solution by
Codex 0 2026-10-31
The doubling mixin delegates with a transformed argument.For
trait DoublePlugin extends Stack {
override def push(x: Int): Unit = super.push(2 * x)
}A, the relevant Scala trait linearization is A, DoublePlugin, SumPlugin, Stack. A push first doubles the argument, then SumPlugin sees and sums that doubled value. The physical values are and the total is .For Equivalently,This is stackable trait modification: the order determines which arguments an outer mixin observes. In particular, the running-total specification from part (b) holds for
B, the Scala trait linearization is B, SumPlugin, DoublePlugin, Stack. SumPlugin sums the original argument, while its super.push doubles the stored value. The physical values are still , but the counter is . Hence the two lines printed are12
6A but does not survive the transforming inner mixin in B. With the given implementation, popping from B would subtract the doubled stored value, demonstrating why mixing individually sensible traits can break a combined representation invariant. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 5 b Solution by
Codex 0 2026-10-31
Keep a running total in the mixin, and use only the inherited public operations. Updating after a successful superclass operation leaves the total unchanged if an empty For this trait mixed directly into
pop fails its precondition.trait SumPlugin extends Stack {
private var total = 0
override def push(x: Int): Unit = {
super.push(x)
total += x
}
override def pop(): Int = {
val x = super.pop()
total -= x
x
}
def sum: Int = total
}Stack, its representation invariant is , where is the abstract stack sequence. A successful push adds exactly its argument and a successful pop removes exactly its returned value. ThusThe mixin reads no private Stack fields. Its operations add constant time complexity. As in the paper's Int interface, an unbounded mathematical total requires excluding integer overflow, or replacing the accumulator with BigInt if the interface is allowed to change. A later mixin can transform a pushed value at a different point in the Scala trait linearization; the effect is analyzed in the next part. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 5 a Solution by
Codex 0 2026-10-31
Use a minimum stack: each linked list node caches the minimum of all values from that node down to the bottom. This handles duplicate minima without searching when one is removed.The representation invariant is that the chain from
class Stack {
private class Node(val value: Int, val least: Int, val next: Node)
private var top: Node = null
def push(x: Int): Unit = {
val m = if (top == null) x else math.min(x, top.least)
top = new Node(x, m, top)
}
def pop(): Int = {
require(!isEmpty)
val x = top.value
top = top.next
x
}
def isEmpty: Boolean = top == null
def min: Int = {
require(!isEmpty)
top.least
}
}top is finite and acyclic, and every reachable node q satisfiesThe abstraction function takes the values along this chain in top-to-bottom order. Thus pushing changes to , and popping a nonempty returns its head and leaves its tail. The cached-minimum equation follows for a new node from the previously valid chain; removing a head leaves every surviving equation unchanged. ConsequentlyThe bounds use the usual constant-cost allocation and integer arithmetic model. Storage is linear in the number of entries. min and pop require a nonempty stack; min leaves it unchanged. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 4 c Solution by
Codex 0 2026-10-31
Use trie search with a letter multiset. Counts avoid generating the same result repeatedly when the input contains equal letters. The backtracking explores a letter value once, irrespective of how many indistinguishable copies remain.At entry to
def allWords(ls: String): List[String] = {
val left = new Array[Int](26)
var i = 0
while (i < ls.length) {
left(toIndex(ls.charAt(i))) += 1
i += 1
}
var out = List.empty[String]
def visit(t: Trie, prefix: String): Unit = {
if (t.complete) out = prefix :: out
var k = 0
while (k < 26) {
val child = t.children(k)
if (left(k) > 0 && child != null) {
left(k) -= 1
visit(child, prefix + toChar(k))
left(k) += 1
}
k += 1
}
}
visit(this, "")
out.reverse
}visit, t is reached by prefix, and left(k) is the input multiplicity of letter minus its multiplicity in prefix. All these counts are nonnegative. Each recursive call consumes one available letter, and restoration makes sibling calls see the same counts as before that child. The recursion therefore terminates at depth at most , even when the dictionary contains longer words.A completed prefix is emitted once, and the traversal continues so that longer words with the same prefix are also found. Every emitted word is in the dictionary and uses at most the available multiplicities. Conversely, every dictionary word meeting those bounds has an existing trie path whose next letter has positive remaining count at every step; the traversal reaches its terminal node. A trie has a unique root path for each word, and each child is visited at most once per reached node. ThereforeThe boxed expression specifies the set of entries in the returned list, rather than its ordering. If the dictionary includes the empty string, the method includes it once. Counting costs ; visiting reachable nodes costs apart from string copying and output construction. The presented immutable-prefix construction additionally costs the sum of the prefix lengths it constructs. The recursion depth is at most , and the letter-count array has fixed size.
Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 4 b Solution by
Codex 0 2026-10-31
Create only the missing nodes along the required trie path, and mark its last node complete.The loop invariant is that the path for
def add(w: String): Unit = {
var t: Trie = this
var i = 0
while (i < w.length) {
val k = toIndex(w.charAt(i))
if (t.children(k) == null) t.children(k) = new Trie
t = t.children(k)
i += 1
}
t.complete = true
}w.take(i) exists and ends at t, while the set of complete words remains unchanged from entry. A fresh node has all children null and complete == false, so creating an unfinished path introduces no stored word. The loop variant is . At termination the full path exists; setting its final flag givesExisting paths and terminal flags are untouched, so adding an existing word is idempotent. Adding the empty string marks the root. The time complexity is because the alphabet size is fixed; at most new trie nodes are allocated. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 4 a Solution by
Codex 0 2026-10-31
Traverse the trie along the input string, stopping as soon as the required child is absent. All words and letter collections in this question are assumed to use only lowercase letters The loop invariant is that
a through z; this is the alphabet represented by the given child array. Put the following methods inside the supplied Trie class.def contains(w: String): Boolean = {
var t: Trie = this
var i = 0
while (i < w.length && t != null) {
t = t.children(toIndex(w.charAt(i)))
i += 1
}
t != null && t.complete
}t is the trie node reached by the prefix w.take(i), or null if that prefix has no path, with . If t is null, no extension can be a stored word. Otherwise, following the next character preserves the loop invariant. The loop variant is . At exit,A prefix that exists but is not complete must return false. The empty string is present exactly when the root's complete flag is true. The time complexity is , with constant auxiliary space. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 3 c Solution by
Codex 0 2026-10-31
Reuse A useful loop invariant is that the pending segments are pairwise disjoint valid array segments of length at least two; the whole array multiset equals its original value; and every inverted pair of positions lies in a single pending segment. Explicitly, if and , some queued contains both and . Thus all ordering obligations are localized to queued segments.
split and swap, but replace recursive calls with a FIFO queue of unresolved segments. Enqueue only segments of length at least two.def quicksortLoop(): Unit = {
val work = scala.collection.mutable.Queue[(Int, Int)]()
if (a.length > 1) work.enqueue((0, a.length))
while (!work.isEmpty) {
val (l, r) = work.dequeue()
val (lt, gt) = split(l, r)
if (lt - l > 1) work.enqueue((l, lt))
if (r - gt > 1) work.enqueue((gt, r))
}
}Initially the only possible pending segment is the whole array. One array partitioning confines all its remaining inversions to its two child segments. Its equal middle segment is ordered, the left/middle/right boundaries have the correct ordering, and segments of length at most one cannot contain an inversion. Because array partitioning only permutes the selected segment's entries, it cannot create an inversion across a boundary that previously had no inversions. This proves preservation of the loop invariant.
The loop variant is the total pending lengthProcessing one segment removes from and adds at most , strictly less because . Thus is a nonnegative integer and decreases. At exit no pending segment exists, so no inversion exists:The FIFO queue itself can grow to pairs. The time complexity is that of the corresponding recursive quicksort, including linear behavior on equal input. Removing recursion does not make the worst-case time complexity linear or eliminate the work storage.
Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 3 b Solution by
Codex 0 2026-10-31
Use three-way quicksort. Fix the pivot value before moving any entries, and group equal entries together so that none needs a recursive call. These helpers and the sorter belong inside an enclosing scope containing The array partitioning loop invariant iswith unclassified. Swaps preserve the segment's array multiset and leave all entries outside it unchanged. The loop variant decreases in every branch. When swapping an entry from the right into
a.def swap(i: Int, j: Int): Unit = {
val t = a(i); a(i) = a(j); a(j) = t
}
def split(l: Int, r: Int): (Int, Int) = {
// Pre: 0 <= l < r <= a.length.
val p = a(l)
var lt = l
var i = l
var gt = r
while (i < gt) {
if (a(i) < p) {
swap(lt, i)
lt += 1
i += 1
} else if (a(i) > p) {
gt -= 1
swap(i, gt)
} else {
i += 1
}
}
(lt, gt)
}
def quicksort(l: Int, r: Int): Unit = {
require(0 <= l && l <= r && r <= a.length)
if (r - l > 1) {
val (lt, gt) = split(l, r)
quicksort(l, lt)
quicksort(gt, r)
}
}
// Sort the whole array with quicksort(0, a.length).i, do not increment i, since the incoming entry still needs classification. At termination,Here the middle segment is nonempty because the original pivot occurs somewhere in the preserved array multiset. Both recursive segments therefore have length strictly smaller than . Mathematical induction on segment length proves that the two recursive calls sort their segments, and their ordering relative to the middle segment proves the complete sorting postcondition.There is no additional array. The call stack and returned boundary pairs still use auxiliary space. An all-equal input takes one linear scan and no nontrivial recursive calls, so its time complexity is for . Balanced partitions give time complexity; a fixed first pivot can still give on adversarial distinct inputs. The worst-case call stack has entries.
Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 3 a Solution by
Codex 0 2026-10-31
Let be a saved mathematical copy of the original array. A precise postcondition for sorting isThe precondition is that
a is a non-null array of length . Its length and every unrelated object remain unchanged. The second clause preserves the array multiset, including all multiplicities; ordering alone would also permit overwriting the entire array with zeros. The saved is a specification device and need not be allocated by the implementation. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 2 a Solution by
Codex 0 2026-10-31
Use an abstract finite sequence of integers, initially empty. In the following trait, comments are the mathematical specification; means the abstract state on entry, and The postcondition of
result means the returned value.trait ReversableBuffer {
// Abstract state s: finite sequence of Int, initially [].
// Pre: true. Post: s = s0 ++ [x].
def append(x: Int): Unit
// Pre: true. Post: s = [x] ++ s0.
def prepend(x: Int): Unit
// Pre: 0 <= i < length(s0).
// Post: result = s0 !! i,
// s = take i s0 ++ drop (i+1) s0.
def get(i: Int): Int
// Pre: true. Post: s = reverse(s0).
def rev: Unit
}get includes removal: it is not merely indexed reading. In particular,Repeated equal values elsewhere in the sequence remain present. List concatenation, indexing and list reversal in the specification describe abstract behavior without committing to a physical representation. Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 1 b Solution by
Codex 0 2026-10-31
A suitable precondition, including the empty array case, isA base of one is allowed: its only valid digit is zero. The empty product is one, so permits exactly . Each Euclidean division extracts the next least significant digit.The expensive precondition is a caller obligation, rather than a separately computed product inside the conversion. Define . The loop invariant istogether with for each processed digit. The Euclidean division identity proves preservation of the reconstruction equality. Positivity of the bases gives the digit bounds and the next bound on the quotient. The loop variant is . At exit, forces , henceThere are exactly remainder operations and divisions, hence counted operations. The returned array occupies space and the remaining auxiliary space is constant. A represented value can never exceed , as follows either by the same nested expansion or by telescoping ; thus the stated range is also necessary for positive bases.
def intToDigs(n: Int, bs: Array[Int]): Array[Int] = {
// Pre: every base is positive, and 0 <= n < product(bs).
val N = bs.length
val xs = new Array[Int](N)
var i = 0
var q = n
while (i < N) {
xs(i) = q % bs(i)
q = q / bs(i)
i += 1
}
xs
} Past exam of the year 1 of the computer science course of the University of Oxford 2013 Imperative programming 1 a Solution by
Codex 0 2026-10-31
Evaluate the mixed radix representation from its most significant end, using Horner's method. There is no need to compute the positional weights separately.The digit conditions are additional preconditions. The loop invariant at the loop test isAn empty sum is zero and an empty product is one, so initialization at is correct. For the next digit, writing giveswhich preserves the loop invariant. The loop variant is . At termination , givingThe code executes exactly multiplications, meeting the bound, and uses auxiliary space. When it returns zero. For the time representation in the source, it computes .
def digsToInt(xs: Array[Int], bs: Array[Int]): Int = {
require(xs.length == bs.length)
val N = xs.length
var i = N
var acc = 0
while (i > 0) {
i -= 1
acc = xs(i) + bs(i) * acc
}
acc
} 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





