Solutions to all eight questions of the 18 June 2014 paper. The Scala fragments use the paper's Scala 2 style. Unless stated otherwise, array dimensions and array indices meet the supplied preconditions, and size calculations fit
Int.[ref]Imperative Programming examination paper by the University of Oxford (2014). Cover and pp. 2–15, Questions 1–8
Every nonempty axis-aligned rectangle has a top-right corner. Enumerate all such corners and retain the greatest area from part (b).The loop invariant is that
var best = 0
var i = 0
while (i < N) {
var j = 0
while (j < N) {
best = math.max(best, maxRectangleAt(i, j))
j += 1
}
i += 1
}best is the greatest area obtained from the corners processed so far, or zero if there are none. ConsequentlyThe preprocessing cost from part (a) is included. An entirely false matrix, or an empty one, returns zero. In the printed example, rows through and columns through give area ; exhaustive corner enumeration confirms that no larger all-true rectangle exists.[ref]Start at the highest level. Move right only while the next key is strictly smaller than At each inner loop head,
x, then descend without returning to head.def findPreds(x: Int): Array[Node] = {
require(Int.MinValue < x && x < Int.MaxValue)
val preds = new Array[Node](NumLevels)
var n = head
var level = NumLevels - 1
while (level >= 0) {
while (n.nexts(level).datum < x) {
n = n.nexts(level)
}
preds(level) = n
level -= 1
}
preds
}n belongs to the current level and has key below x. Every node already passed has key below x. The representation invariant ensures that n also belongs to the next lower level. Stopping just before a key at least x therefore givesThe tail prevents a null dereference during the comparison. Strict comparison makes the predecessor lie before the first occurrence of x, including duplicates. On the source diagram, findPreds(14) yields data , in increasing level order.[ref]Imperative Programming examination paper by the University of Oxford (2014). p. 4, skiplist diagram and Question 3(c)
With the customary random-level policy this has expected logarithmic time complexity for a suitably sized level cap; an unlucky skip list can require a linear scan. No deterministic logarithmic guarantee is claimed.
Apply overload selection followed by dynamic dispatch: the receiver's static type determines visible signatures, and argument static types choose the overload. The receiver's runtime type then selects its override. A
super call starts at the inherited implementation instead of redispatching to the override just entered.For
c.f1(y), the Y overload is visible on C, so its body in C runs. For a.f2(x), the X signature is selected and its override in C calls C.f1(X). For a.f2(y), the Y signature is selected; its body in C calls super.f1(y). The superclass B has only f1(X), so that call uses B.f1(X).For
b.f3(y), the static type B exposes only f3(X). Its override in C calls super.f2(x), entering B.f2(X). The unqualified f1(x) inside that body still uses dynamic dispatch on the original C instance, so it calls C.f1(X).Finally
c.f4(x) invokes the companion object C, which is a separate receiver extending B. Its f3(X) calls inherited B.f2(X), whose f1 call dispatches to ObjC.f1(X).[ref]Imperative Programming examination paper by the University of Oxford (2014). p. 10, Question 6(a), method bodies and calls
