Skip to content
Part IA Michaelmas Term

Tripos 2021 Paper 1 Question 2 — Worked Solution

Question: A W × H matrix can be represented by a flat list concatenating the rows in order. For three alternative representations, state the type T, give create and get, and state asymptotic complexity of get.

Given functional array code:

type 'a tree = Lf | Br of 'a * 'a tree * 'a tree
exception Subscript

let rec sub = function
  | Lf, _ -> raise Subscript
  | Br (v, t1, t2), 1 -> v
  | Br (v, t1, t2), k when k mod 2 = 0 -> sub (t1, k / 2)
  | Br (v, t1, t2), k -> sub (t2, k / 2)

let rec update = function
  | Lf, k, w ->
      if k = 1 then Br (w, Lf, Lf)
      else raise Subscript
  | Br (v, t1, t2), k, w ->
      if k = 1 then Br (w, t1, t2)
      else if k mod 2 = 0 then Br (v, update (t1, k / 2, w), t2)
      else Br (v, t1, update (t2, k / 2, w))

(a) A list of lists [5 marks]

Type T: float list list

create:

let rec create w = function
  | [] -> []
  | m ->
      let row = take w m in
      let rest = drop w m in
      row :: create w rest

This uses take and drop (as defined in the course) to partition the flat list into rows of width w. Each recursive step extracts one row. This is O(W × H) time since we must process every element of the flat list.

get:

let rec get r c m =
  match m with
  | [] -> raise (Invalid_argument "index out of bounds")
  | row :: rest ->
      if r = 0 then
        let rec nth i = function
          | [] -> raise (Invalid_argument "index out of bounds")
          | x :: _ when i = 0 -> x
          | _ :: xs -> nth (i - 1) xs
        in
        nth c row
      else
        get (r - 1) c rest

Asymptotic complexity of get: O(r + W) in the worst case. We scan r rows (skipping r × W elements), then scan c elements in the target row. Since r < H and c < W, this is O(H + W).

(b) An array of arrays [6 marks]

Type T: float array array

create:

let create w m =
  let flat = Array.of_list m in
  let h = Array.length flat / w in
  Array.init h (fun i ->
    Array.init w (fun j ->
      flat.(i * w + j)
    )
  )

Explanation: We first convert the input list to a flat array for O(1) indexed access. Then we allocate an h × w array of arrays, computing each element’s index in the flat array as i * w + j. This is O(W × H) time.

get:

let get r c m =
  m.(r).(c)

Asymptotic complexity of get: O(1). Array indexing is constant-time: the outer array lookup m.(r) and the inner array lookup .(c) are both direct indexed accesses.

(c) A functional array of functional arrays [9 marks]

Type T: float tree tree

A functional array of functional arrays: the outer tree maps row index r+1 to the inner tree (where indices in functional arrays start at 1), and the inner tree maps column index c+1 to the float value. The element at matrix position (r, c) corresponds to index (r+1) in the outer tree and (c+1) in the inner tree.

create:

let create w m =
  let n = List.length m in
  let h = n / w in
  let rec build_rows i rows acc =
    if i >= h then acc
    else
      let row_start = i * w in
      let rec build_row j acc_row =
        if j >= w then acc_row
        else
          let v = List.nth m (row_start + j) in
          build_row (j + 1) (update (acc_row, j + 1, v))
      in
      let row = build_row 0 Lf in
      build_rows (i + 1) rows (update (acc, i + 1, row))
  in
  build_rows 0 h Lf

Explanation (more efficient version): Rather than repeatedly calling update (which copies the path each time, giving O(n log n) overall), we can construct the functional array directly using a more efficient approach. The coursework version using update is acceptable but expensive. Here is a cleaner version:

let create w m =
  let n = List.length m in
  let h = n / w in
  let rec of_list start len =
    if len = 0 then Lf
    else if len = 1 then Br (List.nth m start, Lf, Lf)
    else
      let mid = len / 2 in
      let left = of_list start mid in
      let right = of_list (start + mid + 1) (len - mid - 1) in
      Br (List.nth m (start + mid), left, right)
  in
  let rec make_rows i =
    if i >= h then Lf
    else
      let row = of_list (i * w) w in
      update (make_rows (i + 1), i + 1, row)
  in
  make_rows 0

But the simplest approach building row-by-row with update is:

let create w m =
  let n = List.length m in
  let h = n / w in
  let rec loop i acc =
    if i = h then acc
    else
      let rec build_row j row_acc =
        if j = w then row_acc
        else
          let idx = i * w + j in
          let v = List.nth m idx in
          build_row (j + 1) (update (row_acc, j + 1, v))
      in
      let row = build_row 0 Lf in
      loop (i + 1) (update (acc, i + 1, row))
  in
  loop 0 Lf

Building the outer functional array takes O(H × log H) time (each of H row updates copies O(log H) path nodes). Building each inner row takes O(W × log W) time for each of H rows. Total: O(H × W × log W + H × log H).

get:

let get r c m =
  let row = sub (m, r + 1) in
  sub (row, c + 1)

Asymptotic complexity of get: O(log H + log W). We first look up the row tree at index r+1, which takes O(log H) time following the binary path. Then we look up the inner tree at index c+1, taking O(log W) time. Both sub operations follow paths proportional to the depth of balanced trees, giving O(log H + log W) total. Since H × W = n, this is O(log n) where n is the total number of elements.