Skip to content
Part IA Michaelmas Term

Zip and Unzip

zip

zip pairs up corresponding elements from two lists into a single list of pairs. If the lists differ in length, surplus elements from the longer list are silently discarded.

let rec zip xs ys =
  match xs, ys with
  | (x::xs, y::ys) -> (x, y) :: zip xs ys
  | _ -> []

Type: 'a list -> 'b list -> ('a * 'b) list

Key points:

  • The wildcard pattern _ matches anything and signals that the second clause discards its arguments. Using _ is clearer than a named variable when the value is not used.
  • Pattern order matters: the first clause (x::xs, y::ys) is tried before the wildcard. Only when at least one list is empty does the second clause fire. If the clauses were reversed, zip would always return [].

unzip

unzip reverses the operation, taking a list of pairs and returning a pair of lists:

let rec unzip = function
  | [] -> ([], [])
  | (x, y)::pairs ->
      let xs, ys = unzip pairs in
      (x::xs, y::ys)

Type: ('a * 'b) list -> 'a list * 'b list

The local let binding let xs, ys = unzip pairs in ... destructures the result of the recursive call. In general, let P = E₁ in E₂ matches pattern P against the value of expression E₁ and binds the variables in P within E₂.

conspair helper

An alternative version of unzip extracts the destructuring into a helper function:

let conspair ((x, y), (xs, ys)) = (x::xs, y::ys)

let rec unzip = function
  | [] -> ([], [])
  | xy :: pairs -> conspair (xy, unzip pairs)

conspair takes a pair ((x, y), (xs, ys)) and returns (x::xs, y::ys). While not every local binding can be eliminated this cleanly, the pattern of factoring out helper functions is a useful technique for keeping code readable.