PoP assignment 2: variance three ways and a pizza module in OCaml

My second hand-in for Programmering og problemløsning at KU — one variance function written with a loop, with recursion and with higher-order functions, input partitioning for tests, and a pizza module as an abstract data type.

This post was AI-generated from my hand-in — the report and the OCaml source files.

Course: Programmering og problemløsning (PoP) 2026, University of Copenhagen. The course is graded pass/fail on four assignments, each with a submission and a mandatory resubmission. This is the first submission of assignment 2, handed in on 23 October; the resubmission is due 6 November.

The assignment has two parts: a variance function implemented with each of the three kinds of iteration from class, and a pizza module defined as an abstract data type, both tested with input partitioning.

1. Variance

Specification

The definition comes from the W3Schools page on variance: find the mean, take each element's difference from the mean, square it, and take the mean of the squares. That is the population variance:

My specification:

(* Returns variance given a list of floats *)
calculate_variance : float list -> float
    find the mean
    calculate the difference per element with the mean
    square the difference
    find the mean of the squared differences

Return 0, given an empty list

The empty list is the one decision that needs making. The mean divides by the length, and the length of [] is 0, so I decided to return 0.0 rather than fail.

Three implementations

Week 5 named three ways to iterate: loops, recursion and higher-order functions. All three versions are in variance.ml.

Loop. One function. Two while loops walk the list with a ref to "what's left": the first sums the elements to get the mean, the second sums the squared differences.

let calculate_variance_loop (elements : float list) : float =
  if List.length elements = 0 then 0.0 else
  let sum = ref 0.0 in
  let remaining = ref elements in
  while !remaining <> [] do
    let x = List.hd !remaining in
    sum := !sum +. x;
    remaining := List.tl !remaining
  done;
  let mean = !sum /. float_of_int (List.length elements) in
  (* second loop: same walk, summing (x - mean)^2 *)
  ...

Recursion. Broken into three small functions, each with a case for [] and a case for x :: rest:

let rec sum (elements : float list) : float =
  match elements with
  | [] -> 0.0
  | x :: rest -> x +. sum rest

let rec length (elements : float list) : int =
  match elements with
  | [] -> 0
  | _ :: rest -> 1 + length rest

let rec sum_squared_diff (elements : float list) (mean : float) : float =
  match elements with
  | [] -> 0.0
  | x :: rest -> (x -. mean) *. (x -. mean) +. sum_squared_diff rest mean

let calculate_variance_rec (elements : float list) : float =
  if elements = [] then 0.0 else
  let float_length = float_of_int (length elements) in
  let mean = sum elements /. float_length in
  sum_squared_diff elements mean /. float_length

Higher-order functions. List.fold_left for the sums and List.map for the squared differences:

let calculate_variance_hof (elements : float list) : float =
  if elements = [] then 0.0 else
  let float_length = float_of_int (List.length elements) in
  let sum = List.fold_left (fun acc e -> acc +. e) 0.0 elements in
  let mean = sum /. float_length in
  let squared_diffs = List.map (fun e -> (e -. mean) *. (e -. mean)) elements in
  List.fold_left (fun acc e -> acc +. e) 0.0 squared_diffs /. float_length

The three get progressively shorter. The loop spells out how to walk the list; the fold version only says what to combine, which is the imperative-versus-declarative contrast from week 5.

Tests by input partitioning

Input partitioning means splitting the valid inputs into disjoint classes and picking a representative or two from each. I expected the typical input to be a list of positive floats, so the classes are the inputs at the edges of that:

Class · Input · Expected · Why Empty list · [] · 0.0 · the mean would divide by 0 One element · [5.0] · 0.0 · nothing to vary Negative elements · [-5.0; -2.0] · 2.25 · variance stays non-negative Same elements · [5.0; 5.0; 5.0] · 0.0 · every difference is 0

For the negative class, the mean of and is , both differences are , and the variance is .

The suite follows the test_bubblesort.ml style from class: a list of (class name, [(input, expected)]) pairs, and a runtest that takes the function under test as an argument, so the same suite runs against all three implementations:

runtest calculate_variance_loop;;
runtest calculate_variance_rec;;
runtest calculate_variance_hof;;

All three pass 4 of 4. On the seven dog heights from the W3Schools example, all three give , matching the page.

Bugs found

None when the tests ran, but only because writing the tests sent me back to the code first. The empty-list check was in my specification and not in my first implementations. Without it, [] gives 0.0 /. 0.0, which in OCaml is not an exception but nan, and the function returns it without complaint.

Pitfall: float division by zero doesn't crash in OCaml. 0.0 /. 0.0 is nan and 1.0 /. 0.0 is infinity, so a missing empty-list case shows up as a strange result, not an error.

2. A pizza module

The task: represent what a customer decides when ordering a pizza, plus its price, as an abstract data type, a type that can only be used through the operations in its interface.

The interface

pizza.mli exposes the choices as variants and keeps pizza itself abstract:

(* price depends on: size and crust, 10 for small, 15 for medium,
   20 for large, and filled crust costs an extra 2$ *)
type size = Small | Medium | Large
type toppings = Ham | Pepperoni | Pineapple
type crust = Filled | Regular

type pizza

(* lets the customer select the size, topping and crust *)
val create_pizza : size -> toppings -> crust -> pizza

(* the price, which depends on the selected crust and size *)
val generate_price : pizza -> int

Because type pizza has no = ... in the interface, code outside the module can't build or take apart a pizza directly. It has to go through create_pizza.

The implementation

Inside pizza.ml, a pizza is just a triple. The price ignores the topping:

type pizza = size * toppings * crust

let create_pizza (selected_size : size) (topping : toppings) (selected_crust : crust) : pizza =
  (selected_size, topping, selected_crust)

let generate_price (pizza : pizza) : int =
  let (s, _, c) = pizza in
  let size_price = match s with
    | Small -> 10
    | Medium -> 15
    | Large -> 20
  in
  let crust_price = match c with
    | Regular -> 0
    | Filled -> 2
  in
  size_price + crust_price

Tests

My first idea for partitions was the incomplete orders: a pizza with no size, no crust or no topping. The types rule those out: create_pizza takes exactly one of each, so a pizza with no size doesn't compile. That makes the valid inputs a finite set, combinations, and I tested every one of them:

· Regular · Filled Small · 10 · 12 Medium · 15 · 17 Large · 20 · 22

The same table holds for each of the three toppings, which is the point: the topping must not change the price.

The tests are compiled together with the module, as in week 6:

ocamlc pizza.mli pizza.ml test_pizza.ml -o test_pizza
./test_pizza

and pass 18 of 18.

The hand-in

PoP assignment 2, page 1 PoP assignment 2, page 2 PoP assignment 2, page 3