(* Alias for Printf.printf, because it saves typing characters. *)
let print = Printf.printf;;

(* 1: nth yields the N-th element of the list L, counting from zero. *)
let rec nth l n = match l with
    | [] -> raise (Failure "Index out of bounds")
    | h::t -> if n == 0 then h else nth t (n - 1)
;;

print "7.1: %b\n" ((=) (nth [1;2;3] 2) 3 );;

open List

(* 2: Given a list of non-empty lists L, head yields the list of first elements
 * of the sublists. *)
let heads l = map hd l;;

print "7.2: %b\n" ((=) (heads [[1;2;3];[4;5;6]]) [1;4]);;

(* 3: zip L M that given a pair of lists yields the list of pairs of elements
 * from corresponding list positions. If the length of one argument list
 * exceeds * the length of the other, surplus elements are to be ignored. *)
let rec zip ?(s=[]) l1 l2 = match (l1, l2) with
    | [], _ -> s (* If the first list is empty, return list. *)
    | _, [] -> s (* If the second list is empty, return list. *)
      (* Take the head and tail of both lists. Then call zip with the list,
       * and append a tuple of the two heads to it. The last two arguments
       * of zip are the tails of both lists. *)
    | h1::t1, h2::t2 -> zip ~s:(s @ [h1, h2]) t1 t2
;;

print "7.3: %b\n" ((=) (zip [1;2;3] [true;false]) [(1,true);(2,false)]);;

(* 4: map f l that given a function F applies it to every element of list L
 * returning a list of the results. *)
let rec map f l = match l with
    | [] -> []
    | h::t -> f h::map f t
;;

let inc n = 1 + n in 
print "7.4: %b\n" ((=) (map inc [1;2;3;4;5]) [2;3;4;5;6]);;

(* 5: reduce f z l that given a function f, a begin element z and a list l
 * applies f to z and the first element of l, then applies f to the result of
 * this application and the second element of l, etc. Which means that reduce f
 * z [a;b;c] becomes f (f (f z a) b) c. *)
let rec reduce f z l = match l with
    | [] -> z
    | h::t -> reduce f (f z h) t
;;

print "7.5: %b\n" ((=) (reduce (+) 0 [1;2;3;4;5]) 15);;

(* 6: isSublist l s that checks whether or not the argument list s is a sublist
 * of the argument list l. *)
let isSublist l s = 
    (* Store the full search term *)
    let full = s in
    let rec sublistMatches l s = match (l, s) with
    | _, [] -> true
    | [], _ -> false
    | h1::t1, h2::t2 -> 
        (* if the head of s matches the head of l'. *)
        h1 = h2
        (* and the next head of s matches the next head of l. *)
        && ( t1 = [] && t2 = [] || hd t1 = hd t2 )
        (* recursively match the tails of s and l. *)
        && sublistMatches t1 t2
        (* otherwise, continue with the tail of l and reset s. *)
        || sublistMatches t1 full
    in sublistMatches l s
;;

print "7.6.1: %b\n" (isSublist [1;2;3;4] [3;4]);;
print "7.6.2: %b (%s)\n" (isSublist [1;2;3;4] [2;4]) "expected failure";;
print "7.6.3: %b (%s)\n" (isSublist [1;2;3;3;4] [2;3;4]) "expected failure";;
print "7.6.4: %b\n" (isSublist [1;2;3;3;4] [3;3;4]);;
print "7.6.5: %b\n" (isSublist [] []);;

(* 7. lookup l key that given a list l of (key, value) tuples and a key returns
 * the value associated with that key. *)
let rec lookup l key = match l with
    | [] -> raise (Failure "Key not found")
    | h::t -> let k,v = h in if key = k then v else lookup t key
;;

print "7.7: %b\n" ((=) (lookup [(1,"foo");(2,"bar");(3,"baz")] 2) "bar");;
