"foundations of CS supervision 4"

Written By Atticus Kuhn
Tags: "public", "project"
:PROPERTIES: :ID: 84c4452d-e37f-4029-8739-e063bc22bcf4 :mtime: 20231124170129 20231110164438 20231109171655 20231109120736 20231108173854 20231108063537 20231108053343 20231108012643 20231101015829 20231031094921 :ctime: 20231031094916 :END: #+title: foundations of CS supervision 4 #+filetags: :public:project: * Questions 2010 Paper 1 Question 1 2010 Paper 1 Question 2 2016 Paper 1 Question 2 2020 Paper 1 Question 1 * 2010 Paper 1 Question 1 ** Question Foundations of Computer Science (a) Give an ML datatype declaration suitable for representing lazy lists, possibly of infinite length. [2 marks] (b) Code the ML function interleave, which takes two lazy lists and generates a lazy list containing each of their elements. [2 marks] (c) Code an ML function that applies a given function to every element of a lazy list, returning a lazy list of the results (analogously to the function map). [3 marks] (d ) Code the ML function iterates which, given a function f and some value x, generates a lazy list containing all the values of the form f n(x) (that is, f (· · · f (x) · · ·) with n applications of f ) for $n \ge 0$. [3 marks] (e) Code the ML function iterates2 which, given functions f and g and values x and y, generates a lazy list containing all the values of the form (f m(x), gn(y)) for $m, n \ge 0$. [10 marks] All ML code must be explained clearly and should be free of needless complexity. 1 ** Solution (a) A lazy list uses unit functions to delay the evaluation of the tail. #+BEGIN_SRC ocaml type 'a llist = | Nil | Cons of 'a * (unit -> 'a llist) #+END_SRC #+RESULTS: : type 'a llist = Nil | Cons of 'a * (unit -> 'a llist) (b) Interleave weaves two lazy lists togther. #+BEGIN_SRC ocaml let rec interleave = function | Nil , x -> x | x , Nil -> x | Cons (a , b) , Cons (c , d) -> Cons (a , fun _ -> Cons (c , fun _ -> interleave (b () , d ()))) #+END_SRC #+RESULTS: : <fun> (c) Map applies f to every element of a lazy list. #+BEGIN_SRC ocaml let rec map f = function | Nil -> Nil | Cons (a , b) -> Cons (f a , fun _ -> map f (b ())) #+END_SRC #+RESULTS: : <fun> (d) Iterates creates an infinite lazy list of x , f x , f (f x) , f (f (f x)) , .... #+BEGIN_SRC ocaml let rec iterates f x = Cons (x , fun _ -> iterates f (f x)) #+END_SRC #+RESULTS: : <fun> (e) Create all combinates of $f^n$ and $g^m$. This solution includes some elements multiple times in the list, but the instructions just specified that each pair had to be included at least once, so I think this flaw is fine. #+BEGIN_SRC ocaml let first f (a , b) = (f a , b) let second f (a , b) = ( a , f b) let rec iterates2 f g x y = Cons((x , y) , fun _ -> interleave ((map (first f) (iterates2 f g x y)) , (map (second g) (iterates2 f g x y))) ) #+END_SRC #+RESULTS: : <fun> A simple utility function to inspect lazy lists. #+BEGIN_SRC ocaml let rec view n = function | Nil -> [] | Cons (a , b) -> if n <= 0 then [] else (a :: (view (n - 1) (b ()))) #+END_SRC #+RESULTS: : <fun> Count up infinitly from a starting value. #+BEGIN_SRC ocaml let rec from n = Cons(n , fun _ -> from (n + 1)) #+END_SRC #+RESULTS: : <fun> Test if the map function works. #+BEGIN_SRC ocaml :exports both view 10 (map succ (from 0)) #+END_SRC #+RESULTS: | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Test is interleave works. #+BEGIN_SRC ocaml :exports both view 10 (interleave ((from 0 ) ,(map succ (from 10)))) #+END_SRC #+RESULTS: | 0 | 11 | 1 | 12 | 2 | 13 | 3 | 14 | 4 | 15 | Test if iterates works. #+BEGIN_SRC ocaml :exports both view 10 (iterates succ 0) #+END_SRC #+RESULTS: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Test if iterates2 works. #+BEGIN_SRC ocaml :exports both view 10 (iterates2 succ succ 0 0) #+END_SRC #+RESULTS: | 0 | 0 | | 1 | 0 | | 0 | 1 | | 2 | 0 | | 1 | 1 | | 1 | 1 | | 0 | 2 | | 3 | 0 | | 2 | 1 | | 2 | 1 | * 2010 Paper 1 Question 2 ** Question (a) Write brief notes on the function defined below: #+BEGIN_SRC ocaml let rec foldl f e = function | [] -> e | x :: xs -> foldl f (f e x) xs #+END_SRC #+RESULTS: : <fun> Illustrate your answer by describing the computations performed by the following two functions: #+BEGIN_SRC ocaml let f = foldl (foldl (fun a b -> a * b) ) 1 #+END_SRC #+RESULTS: : <fun> #+BEGIN_SRC ocaml :exports both f [[3 ; 4] ; [5 ]] #+END_SRC #+RESULTS: : 60 #+BEGIN_SRC ocaml let g p = foldl (fun (x,y) z -> if p z then (z::x,y) else (x,z::y)) ([],[]) #+END_SRC #+RESULTS: : <fun> #+BEGIN_SRC ocaml :exports both let isEven x = (x mod 2 = 0);; g isEven [ 1 ; 2 ; 3 ;4 ; 5 ; 6 ; 7 ; 8 ; 9] #+END_SRC #+RESULTS: | 8 | 6 | 4 | 2 | | | 9 | 7 | 5 | 3 | 1 | [4 marks] (b) Selection sort is a sorting algorithm that works by repeatedly identifying and setting aside the smallest (or largest) item to be sorted. Implement selection sort in ML and describe the efficiency of your solution using O-notation. [4 marks] (c) Code an ML function to generate a multiplication table in the form of a list of lists of integers. For example, given the argument 3 it should return [[1, 2, 3], [2, 4, 6], [3, 6, 9]]. [6 marks] (d ) Modify your solution to part (c) in order to generate a three-dimensional table containing values xijk computed by calling a supplied 3-argument curried function f . For example, given the argument 2 it should return [[[x111,x112], [x121,x122]], [[x211,x212], [x221,x222]]]. [6 marks] All ML code must be explained clearly and should be free of needless complexity. 1 ** Solution (a) foldl is the unique catamorphism from the initial algebra of the list datatype. The first function f takes in a list of lists of integers, and multiplies all the integers. The second function g takes a predicate p and a list, and parititions the list into those elements that satisfy p and those elements which do not. (b) I used paritition_minimum to create selection_sort. #+BEGIN_SRC ocaml exception EmptyList let min x y = if x < y then x else y let max x y = if x < y then y else x let rec partition_minimum = function | [] -> raise EmptyList | x :: xs -> List.fold_left (fun (current_min , rest) elem -> (min current_min elem , max current_min elem :: rest)) (x , []) xs let rec selection_sort xs = if xs = [] then [] else let (y , ys) = partition_minimum xs in y :: (selection_sort ys) #+END_SRC #+RESULTS: : <fun> Test if partition_minimum works correctly. #+begin_src ocaml :exports both partition_minimum [1 ; 2] #+end_src #+RESULTS: | 1 | (2) | Test if selection_sort works correctly. #+begin_src ocaml :exports both selection_sort [ 4 ; 2 ; 6 ; 1 ; 6] #+end_src #+RESULTS: | 1 | 2 | 4 | 6 | 6 | Efficiency of selection sort is $O(n^2)$. (c) I used range to create multiplication_table. #+BEGIN_SRC ocaml let rec range n = if n = 0 then [] else range (n - 1) @ [n] let rec row length step = if length = 0 then [] else step :: List.map ((+) step) (row (length - 1) step) let multiplication_table n = List.map (row n) (range n) #+END_SRC #+RESULTS: : <fun> Test if multiplication_table works correctly. #+BEGIN_SRC ocaml :exports both multiplication_table 5 #+END_SRC #+RESULTS: | 1 | 2 | 3 | 4 | 5 | | 2 | 4 | 6 | 8 | 10 | | 3 | 6 | 9 | 12 | 15 | | 4 | 8 | 12 | 16 | 20 | | 5 | 10 | 15 | 20 | 25 | (d) This function is a bit complicated, so I will not explain how it works. #+BEGIN_SRC ocaml let cube (f : int -> int -> int -> 'a) (n : int) : 'a list list list = List.map (fun row -> List.map (fun (a , b) -> List.map (fun x ->f x a b ) (range n) ) row ) (List.map (fun x -> List.map (fun y -> (x , y)) (range n)) (range n)) #+END_SRC #+RESULTS: : <fun> Test if cube works correctly. #+BEGIN_SRC ocaml :exports both cube (fun x a b -> (x , a ,b)) 2 #+END_SRC #+RESULTS: | ((1 1 1) (2 1 1)) | ((1 1 2) (2 1 2)) | | ((1 2 1) (2 2 1)) | ((1 2 2) (2 2 2)) | * 2016 Paper 1 Question 2 ** Question (a) A prime number sieve is an algorithm for finding all prime numbers up to a given limit n. The algorithm maintains a list, which initially holds the integers from 2 to n. The following step is then repeated: remove the head of this list, which will be a prime number, and remove all its multiples from the list. Write code for the algorithm above as an ML function of type int -> int list. [4 marks] (b) Consider the problem of eliminating all duplicates from a list of strings. Write code for a function of type string list -> string list such that the output contains the same elements as the input, possibly reordered, but where every element occurs exactly once. The worst-case performance must be better than quadratic in the length of the list. [6 marks] (c) Consider the task of determining whether a given word (a string) can be expressed by joining together various chunks (non-empty strings). If the chunks are abra, cad and hal, then the word abracadabra can be expressed as abra|cad|abra. Note that if the available chunks are ab, bra, cad and abra, then the first two are no good for expressing abracadabra, and yet a solution can be found using cad and abra. The chunks can be used any number of times and in any order. Write code for a function that takes a list of chunks along with a word, and returns a list of chunks that yield the word when concatenated. For the examples above, the result should be ["abra", "cad", "abra"]. Exception Fail should be raised if no solution exists. [10 marks] Note: You are given a function delPrefix for removing an initial part of a string. For example, delPrefix ("abra", "abracadabra") returns "cadabra", but delPrefix ("bra", "abracadabra") raises exception Fail. All ML code must be explained clearly and should be free of needless complexity. Well-known utility functions may be assumed to be available. ** Solution (a) sieve can be expressed concisely using a fold_right. We simply remove all multiples of a from the accumulator for each a. #+BEGIN_SRC ocaml let remove_multiples p = List.filter (fun q -> q mod p != 0) let rec range a b = if b < a then [] else a :: (range (a + 1) b) let sieve n = List.fold_right (fun a acc -> a :: (remove_multiples a acc)) n [] let prime_sieve n = sieve (range 2 n) #+END_SRC #+RESULTS: : <fun> Test if prime_sieve works correctly. #+BEGIN_SRC ocaml :exports both prime_sieve 100 #+END_SRC #+RESULTS: | 2 | 3 | 5 | 7 | 11 | 13 | 17 | 19 | 23 | 29 | 31 | 37 | 41 | 43 | 47 | 53 | 59 | 61 | 67 | 71 | 73 | 79 | 83 | 89 | 97 | (b) To make remove_dups faster, we sort the list first, giving us a runtime complexity of $O(n \log n)$. #+BEGIN_SRC ocaml let rec remove_sorted = function | [] -> [] | [x] -> [x] | a :: b :: cs -> (if a = b then [] else [a]) @ remove_sorted (b :: cs) let remove_dups (s : string list) : string list = remove_sorted (List.sort compare s) #+END_SRC #+RESULTS: : <fun> Test if remove_dups works correctly. #+BEGIN_SRC ocaml :exports both remove_dups ["a" ; "b" ; "a" ; "c" ; "d" ; "b" ; "c" ; "x" ; "d"] #+END_SRC #+RESULTS: | a | b | c | d | x | (c) I coded delPrefix myself, just for testing purposes. #+BEGIN_SRC ocaml exception Fail let delPrefix prefix str = if String.starts_with prefix str then String.sub str (String.length prefix) (String.length str - String.length prefix) else raise Fail #+END_SRC #+RESULTS: : <fun> Check if delPrefix works correctly. #+BEGIN_SRC ocaml :exports both delPrefix "abra" "abracadabra" #+END_SRC #+RESULTS: : cadabra Check if delPrefix works correctly. #+BEGIN_SRC ocaml :exports both delPrefix "abra" "abra" #+END_SRC #+RESULTS: We use concat_map (which is the monadic bind for lists) in order to create from_chunks. #+BEGIN_SRC ocaml let rec from_chunks (s : string) (chunks : string list) : string list list = if s = "" then [[]] else List.concat_map (fun chunk -> try List.map (List.cons chunk) (from_chunks (delPrefix chunk s) chunks) with Fail -> []) chunks #+END_SRC #+RESULTS: : <fun> #+BEGIN_SRC ocaml let rec from_chunks_backtrack (s : string) = function | [] -> if s = "" then [] else raise Fail | f :: t -> try from_chunks_backtrack (delPrefix s f) (f :: t) with Fail -> from_chunks_backtrack s t #+END_SRC #+RESULTS: : <fun> Test if from_chunks works correctly. #+BEGIN_SRC ocaml from_chunks "abracadabra" ["ab" ; "bra" ; "cad" ; "abra"] #+END_SRC #+RESULTS: | abra | cad | abra | #+BEGIN_SRC ocaml from_chunks_backtrack "abracadabra" ["ab" ; "bra" ; "cad" ; "abra"] #+END_SRC #+RESULTS: : Exception: Fail. * 2020 Paper 1 Question 1 ** Question Foundations of Computer Science (avsm2) You need to write OCaml code to help a local park ranger count the different types of trees present in a region of Cambridgeshire woodland. (a) Define an OCaml type tree that can distinguish between an oak, birch or maple tree, and also any other species with an arbitrary string name. [1 mark] (b) Define two OCaml values with the following signatures: (i) val describe : tree -> string that accepts a tree parameter and returns a human-readable string. (ii) val identify : string -> tree that accepts a lowercase string parameter and returns a tree. Explain briefly how the OCaml compiler can statically check if you have handled all the input possibilities for the input parameters to describe and identify. [4 marks] (c) Define a type stree that only distinguishes between three species oak, birch or maple and no others. Implement functions for the following signatures with similar functionality to the earlier identify function: val identify exn : string -> stree val identify opt : string -> stree option Briefly discuss the tradeoffs between your two approaches. [5 marks] (d ) You now need to implement a simple simulator before starting real surveys. Trees will occur in the following fixed infinite sequence: oak, birch, oak, maple, maple, and then repeat from the beginning. (i) Define a function val spotter : unit -> stree that will return the sequence of trees when called multiple times. [5 marks] (ii) Define a purely functional alternative spotter that calculates the next stree in sequence, using only the input arguments to the function to calculate the return value. Write down an example application of this function with the input arguments and the expected output result.(Hint: you may need to pass in the complete sequence as one of the arguments.) [5 marks] 1 ** Solution (a) Define an inductive datatype. #+BEGIN_SRC ocaml type tree = | Oak | Birch | Maple | Other of string #+END_SRC #+RESULTS: : type tree = Oak | Birch | Maple | Other of string (a.i) Use pattern matching on the tree type. #+BEGIN_SRC ocaml let describe = function | Oak -> "Oak" | Birch -> "Birch" | Maple -> "Maple" | Other s -> s #+END_SRC #+RESULTS: : <fun> (a.ii) Use pattern matching on strings #+BEGIN_SRC ocaml let identify = function | "Oak" -> Oak | "Birch" -> Birch | "Maple" -> Maple | s -> Other s #+END_SRC #+RESULTS: : <fun> It is quite interesting how the ocaml compiler can check the exhaustiveness of a pattern match, especially for infinite types such as int or string. Here is how I think the ocaml compiler works: If the datatype is a finite datatype, it is easy to check all possibilites. If the datatype is an infinite datatype, ocaml checks if there is one catchall case at the end. (c) #+BEGIN_SRC ocaml exception Tree let identify_exn = function | "Oak" -> Oak | "Birch" -> Birch | "Maple" -> Maple | s -> raise Tree #+END_SRC #+RESULTS: : <fun> #+BEGIN_SRC ocaml let identify_opt = function | "Oak" -> Some Oak | "Birch" -> Some Birch | "Maple" -> Some Maple | s -> None #+END_SRC #+RESULTS: : <fun> I would say that indentify_opt is the better function because in ocaml, exceptions are sneakily hidden away in the type signature. You cannot tell if a function is safe or unsafe merely from looking at the type signature. (d.i) spotter uses some hidden internatl state in order to track the current tree. #+BEGIN_SRC ocaml let spotter = let c = ref 0 in fun () -> begin c := succ !c ; List.nth [Oak ; Birch ; Oak ; Maple ; Maple] (!c mod 5) end #+END_SRC #+RESULTS: : <fun> #+BEGIN_SRC ocaml :export both spotter () #+END_SRC #+RESULTS: : Birch fun_spotter needs an additional input, which is the current state in order to remember which state to go to next. #+BEGIN_SRC ocaml let fun_spotter = function | Birch , _ -> Oak , 1 | Oak , 0 -> Birch , 0 | Maple , 0 -> Maple , 1 | Oak , _ -> Maple , 0 | Maple , _ -> Oak , 0 #+END_SRC #+RESULTS: : <fun> Simply test if fun_spotter works correctly. #+BEGIN_SRC ocaml :exports both let rec rep n f x = if n = 0 then [] else x :: rep (n - 1) f (f x) ;; rep 10 fun_spotter (Oak , 0) #+END_SRC #+RESULTS: | Oak | 0 | | Birch | 0 | | Oak | 1 | | Maple | 0 | | Maple | 1 | | Oak | 0 | | Birch | 0 | | Oak | 1 | | Maple | 0 | | Maple | 1 | * 1997 Paper 1 Question 5. ** Question Noughts and Crosses is a game played by two players (O and X) on a board with nine positions numbered as follows: 1 2 3 4 5 6 7 8 9 The players place their marks (O and X) in unoccupied positions on the board until the game is complete. A completed game is when either (a) there is a straight line of three Xs giving a win for X, or (b) there is a straight line of three Os giving a win for O, or (c) all nine positions are occupied, in which case the game is drawn. O is the first player to move. It is required to construct an OCaml structure representing the tree of all possible games. Each node of the tree should represent a reachable board state, with the root being the empty board, and the leaf nodes corresponding to won, lost or drawn games. (a) Define the OCaml variant type tree that you would use to represent this game tree. [3 marks] (b) Define the function mktree : unit -> tree to construct the complete game tree, explaining carefully how it works. There is no need for your implementation to be efficient in either space or time. [10 marks] (c) Briefly discuss ways in which your implementation of mktree could be made more efficient. [4 marks] (d) Define a function winner is O : tree -> int which when applied to the complete tree will yield the number of distinct games in which O wins. [3 marks] 1 ** Solution (a) Here are the types that I used to model the game #+BEGIN_SRC ocaml type player = X | O type endstate = | Over of player | Draw type cell = Gone of player | Empty type boardstate = cell list type tree = Leaf of endstate | Board of boardstate * (tree list) #+END_SRC #+RESULTS: : type player = X | O : type endstate = Over of player | Draw : type cell = Gone of player | Empty : type boardstate = cell list : type tree = Leaf of endstate | Board of boardstate * tree list (b) The beginning state of the board when the game starts #+BEGIN_SRC ocaml let openingState = [ Empty ; Empty ; Empty ; Empty ; Empty ; Empty ; Empty ; Empty ; Empty ] #+END_SRC #+RESULTS: | Empty | Empty | Empty | Empty | Empty | Empty | Empty | Empty | Empty | A list of all ways a player could win with 3 in a row. #+BEGIN_SRC ocaml let rows = [ [0 ; 1 ; 2] ; [3 ; 4 ; 5] ; [6 ; 7 ; 8] ; [0 ; 3 ; 6] ; [1 ; 4 ; 7] ; [2 ; 5 ; 8] ; [0 ; 4 ; 8] ; [2 ; 4 ; 6] ; ] #+END_SRC #+RESULTS: | 0 | 1 | 2 | | 3 | 4 | 5 | | 6 | 7 | 8 | | 0 | 3 | 6 | | 1 | 4 | 7 | | 2 | 5 | 8 | | 0 | 4 | 8 | | 2 | 4 | 6 | A list of all indices of cells in the board. #+BEGIN_SRC ocaml let range = [0 ; 1 ; 2 ; 3 ; 4 ; 5 ; 6 ; 7 ; 8] #+END_SRC #+RESULTS: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | A simple utility function which sets the ith index of a list. #+BEGIN_SRC ocaml let set_elem l i x = List.mapi (fun j el -> (if (i = j) then x else el)) l #+END_SRC #+RESULTS: : <fun> join 2 boardstates together. #+BEGIN_SRC ocaml let joinBoards (b : boardstate) (p : player) (place : int) : boardstate option = match List.nth b place with | Gone _ -> None | Empty -> Some (set_elem b place (Gone p)) #+END_SRC #+RESULTS: : <fun> Get a list of the next boards reachable from this boardstate. #+BEGIN_SRC ocaml let nextBoards (b : boardstate) (p : player) : boardstate list = List.filter_map (joinBoards b p) range #+END_SRC #+RESULTS: : <fun> Check if a row will result in a win. #+BEGIN_SRC ocaml let isRowWin = function | [Gone X ; Gone X ; Gone X] -> Some X | [Gone O ; Gone O ; Gone O] -> Some O | _ -> None #+END_SRC #+RESULTS: : <fun> Check if the game is in a draw. #+BEGIN_SRC ocaml let isDraw (b : boardstate) : bool = List.for_all (fun cell -> match cell with | Empty -> false | Gone _ -> true) b #+END_SRC #+RESULTS: : <fun> Calculate the endstate (if the game is over) for a given boardstate. #+BEGIN_SRC ocaml let endState (b : boardstate) : endstate option = if isDraw b then Some Draw else let possibleWins : cell list list = (List.map (fun win -> List.map (fun r -> List.nth b r) win) rows) in Option.map (fun x -> Over x) (List.find_map isRowWin possibleWins) (* let winstate (b : board) = List.map (fun row -> ) rows *) #+END_SRC #+RESULTS: : <fun> Get the next player from the current player. #+BEGIN_SRC ocaml let nextPlayer : player -> player = function | X -> O | O -> X #+END_SRC #+RESULTS: : <fun> Recursively find all states #+BEGIN_SRC ocaml let rec possibleStates (b : boardstate) (p : player ) : tree list = let e = endState b in match e with | None -> let boards = nextBoards b p in List.concat_map (fun board -> possibleStates (board) (nextPlayer p)) boards | Some ends -> [Leaf ends] #+END_SRC #+RESULTS: : <fun> Generate the large tree. #+BEGIN_SRC ocaml let mktree (u : unit) : tree = Board (openingState , possibleStates openingState X ) #+END_SRC #+RESULTS: : <fun> (c) To make this more efficient, I would implement lazy generation of the tree, i.e. I would only generate the parts of the tree that we need to generate. It is very unlikely that a person needs to know the entire game tree of tic tac toe, so it would be more economical to only generate the tree upon request. (d) #+BEGIN_SRC ocaml let sum = List.fold_left (fun a b -> a + b) 0 let rec count_O_wins : tree -> int = function | Leaf (Over O) -> 1 | Leaf _ -> 0 | Board (_ , ts) -> sum (List.map count_O_wins ts) #+END_SRC #+RESULTS: : <fun> #+BEGIN_SRC ocaml count_O_wins (mktree ()) #+END_SRC #+RESULTS: : Stack overflow during evaluation (looping recursion?).

Leave your Feedback in the Comments Section