open List

(* 1 *)
let rec nth l n =
    (* Walk through the list recursively, subtracting 1 off n each time and
     * matching it with 0 to check of the nth element has been reached yet *)
    match l with
    | [] -> raise (Failure "list index out of bounds")
    | h::t -> if n = 0 then h else nth t (n - 1)
;;

(* 2 *)
let heads l = map hd l;;

(* 3 *)
let zip l m =
    (* Cut off a list l at length e *)
    let rec cut l e = if e = (length l) then l else cut (rev (tl (rev l))) e in
    (* Get the minimum of two integers *)
    let min a b = if a > b then b else a in
    (* Cut off both lists at the length of the shortest list to equal their
     * lengths, then use the native 'combine' function *)
    let len = min (length l) (length m) in
    combine (cut l len) (cut m len)
;;

(* 4 *)
let rec map f l =
    (* Apply f to the head, then map it to the tail recursively *)
    match l with
    | [] -> []
    | h::t -> f h::map f t
;;

(* 5 *)
let rec reduce f z l =
    (* Apply f to the head, and the reducement of the tail *)
    match l with
    | [] -> z
    | h::t -> f z (reduce f h t)
;;

(* 6 *)
let rec isSublist l s =
    (* Check if l starts with s, otherwise check the tail recursively *)
    let rec startsWith l s =
        (length s) <= (length l) && match s with
        | [] -> true
        | h::t -> h = (hd l) && startsWith (tl l) t
    in
    match l with
    | [] -> false
    | _::t -> (startsWith l s) || (isSublist t s)
;;

(* 7 *)
let rec lookup l key =
    (* Match key to the first tuple and return the value-side of the tuple if
    * matched. Otherwise, lookup in the rest of the tuples *)
    match l with
    | [] -> raise (Failure "lookup")
    | h::t -> let k, v = h in if k = key then v else lookup t key
;;

(* Challenge *)
let rec coin_permutation l n =
    (* Reduce the list using the fact that the the total number of coin
     * permutations is equal to the number of permutations given a single coin,
     * combined with the number of permutations without that coin *)
    match l with
    | [] -> 0
    | h::t -> (if h > n then 0 else 1 + (coin_permutation t (n - h)))
        + (coin_permutation t n)
;;
