solution

CIS 120 Midterm I
September 30, 2016
SOLUTIONS
1
1. Binary Search Trees (15 points)
This problem concerns binary search trees as implemented by the code given in the Appendix, which should be familiar to you from the algorithms in class and Homework 3.
We also use the following helper function to construct examples:
let leaf (x:’a) : ’a tree = Node(Empty, x, Empty)
For each of the following tree expressions, check the box of the tree corresponding to the
expression. Note that, as usual, we omit the Empty constructors from the pictures.
Grading Scheme: All or nothing: 4 points each.
a. let t1 : int tree = insert 1 (insert 2 (insert 3 Empty))
1
\
2
\
3
2
/ \
1
3
3
3
/ \
1
2
/
2
/
1
b. let t2 : int tree = delete 3 (Node(Node(Empty,1,leaf 2), 3, leaf 4))
2
/ \
1
4
1
\
2
\
4
4
/
1
\
2
2
\
4
/
1
c. let t3 : int tree = insert 3 (Node(leaf 4, 2, leaf 5))
2
/ \
4
5
/
3
2
/ \
3
5
\
4
2
/ \
4
5
/
3
3
/ \
2
4
\
5
d. How many of the trees pictured for parts a–c satisfy the binary search tree invariants?
Your answer should be a number from 0 to 12 (inclusive):
The number of BSTs pictured above is ________7__________.
Grading Scheme: “7” = 3 points, “6” or “8” = 2 points, “5” or “9” = 1 point
2
The BAG abstract type
The next few questions on this exam use a variant of the SET abstract type called a BAG (or, in math,
a multiset). A bag of elements is a container abstraction in which the number of occurrences of
each element matters, but their order does not.
We use the notation *x1 , x2 , . . . , xn + for the bag whose elements are given by x1 through xn
(not necessarily distinct). For example, using this notation we have the following bag (in)equalities:
*1, 1, 2+ = *1, 2, 1+ = *2, 1, 1 +
* 1, 1, 2+ 6= *1, 2+ 6= *1, 3+
but
The interface (a.k.a. signature) of the BAG abstract type is:
module type BAG =
type ’a bag
val empty : ’a
val add
: ’a
val remove : ’a
val count : ’a
val equals : ’a
end
sig
bag
-> ’a bag
-> ’a bag
-> ’a bag
bag -> ’a
-> ’a bag
-> ’a bag
-> int
bag -> bool
(∗ An empty bag ∗)
(∗ Add one occurrence of elt to the bag ∗)
(∗ Remove one occurence of elt from the bag ∗)
(∗ Get the number of occurrences of elt in the bag ∗)
(∗ Are the two bags equal? ∗)
A complete, correct implementation of this interace that uses an (unordered) list as the representation type for a bag is given below for your reference.
(∗ Unordered List Bag ∗)
module ULBag : BAG = struct
type ’a bag = ’a list
(∗ INVARIANT: no additional invariant ∗)
let empty : ’a bag = []
let add (x:’a) (b:’a bag) : ’a bag = x::b
let rec remove (x:’a) (b:’a bag) : ’a bag =
begin match b with
| [] -> []
| y::ys -> if x = y then ys else y :: (remove x ys)
end
let rec count (x:’a) (b:’a bag) : int =
begin match b with
| [] -> 0
| y::ys -> if x = y then 1 + (count x ys) else (count x ys)
end
let all (f:’a -> bool) (l:’a list) : bool =
fold (fun x acc -> (f x) && acc) true l
let equals (b1:’a bag) (b2:’a bag) : bool =
all (fun x -> count x b2 = count x b1) b1 &&
all (fun x -> count x b2 = count x b1) b2
end
3
2. Types (14 points)
For each OCaml value below, fill in the blank where the type annotation could go or write
“ill typed” if there is a type error on that line. Your answer should be the most specific type
possible, i.e. int list instead of ’a list.
Some of these expressions refer to values specified by the BAG interface given above. You
may assume that all of the definitions below appear outside of a module that implements this
interface, such as ULBag, and that this module has already been opened.
We have done the first one for you.
;; open ULBag
let z : __________ ’a bag _____________ = empty
let a : ______ill typed_________________ = 1::2::3
let b : __(int bag) * (bool bag)_______ = (add 3 empty, add true empty)
let c : ____int bag bag ___ = add (add 1 empty) empty
let d : ___ill_typed______________ = ULBag.all (fun x -> x > 0) empty
let e : ____ill typed_____________ =
begin match (remove 1 empty) with
| [] -> true
| _ -> false
end
let f : ___int bag -> int _______ = count 3
let g : ___(’a bag) -> bool______ = (fun x -> equals x x)
Grading Scheme: 2 points each. 1 points partial credit for “close, but not quite”
4
3. Higher-order Functions (18 points)
Recall the higher-order list processing functions:
let rec transform (f: ’a -> ’b) (l: ’a list): ’b list =
begin match l with
| [] -> []
| h :: t -> (f h) :: (transform f t)
end
let rec fold (combine: ’a -> ’b -> ’b) (base:’b) (l : ’a list) : ’b =
begin match l with
| [] -> base
| h :: t -> combine h (fold combine base t)
end
For each of the functions below, mark all of the given choices that implement the same
behavior. There may be zero, one, or multiple such functions.
a. let rec take_while (f:’a -> bool) (l:’a list) : ’a list =
begin match l with
| [] -> []
| x::xs -> if f x then x :: (take_while f xs) else []
end
let take_while (f:’a -> bool) (lst:’a list) : ’a list =
transform (fun x -> if (f x) then [x] else []) lst
let take_while (f:’a -> bool) (lst:’a list) : ’a list =
fold (fun x acc -> (f x) :: acc) [] lst
let take_while (f:’a -> bool) (lst:’a list) : ’a list =
fold (fun x acc -> if f x then x :: acc else []) [] lst
5
b. (∗ From inside the ULBag implementation ∗)
type ’a bag = ’a list
let rec remove (x:’a) (b:’a bag) : ’a bag =
begin match b with
| [] -> []
| y::ys -> if x = y then ys else y :: (remove x ys)
end
let remove (x:’a) (b:’a bag) : ’a bag =
fold (fun y acc -> if x = y then acc else (y::acc)) [] b
let remove (x:’a) (b:’a bag) : ’a bag =
fold (fun y acc -> if x = y then ys else y :: acc) [] b
let remove (x:’a) (b:’a bag) : ’a bag =
transform (fun y -> x = y) b
c. (∗ Also from inside the ULBag implementation ∗)
type ’a bag = ’a list
let rec count (x:’a) (b:’a bag) : int =
begin match b with
| [] -> 0
| y::ys -> if x = y then 1 + (count x ys) else (count x ys)
end
let count (x:’a) (b:’a bag) : int =
fold (fun y acc -> if x = y then 1 + acc else acc) 0 b
let count (x:’a) (b:’a bag) : int =
fold (fun y acc -> acc + (if x = y then 1 else 0)) 0 b
let count (x:’a) (b:’a bag) : int =
transform (fun y -> if x = y then 1 else 0) b
Grading Scheme: 2 points per box
6
4. Abstract Types, Invariants, and Modularity (24 points total)
Suppose that we wanted to implement the BAG interface using a different representation that
is more efficient in some circumstances. In particular, we can represent a bag *xn , . . . , xn +
as a ordered list of pairs (y1 , c1 ) :: (y2 , c2 ) :: . . . :: (ym , cm ) :: []. We call this an “ordered
pair bag” and we use the following type definition to start the module:
module OPBag : BAG = struct
type ’a bag = (’a * int) list
Moreover, the implementation is intended to maintain the following invariants:
Inv1. Each pair (yi , ci ) appearing in the list represents ci occurrences of element yi in the bag.
Inv2. The list is sorted so that y1 < y2 < . . . < ym .
Inv3. Each ci > 0 (so there is no pair if there are zero occurrences of an element).
For example, the bag *"a","b","a"+ would be represented by the list [("a",2); ("b",1)]
because there are two occurrences of "a" and one occurence of "b". Adding another "a"
would yield the bag representation [("a",3); ("b",1)], but if we instead added "c" we
would get the list [("a",2); ("b",1); ("c",1)]. If, instead of adding any elements we
remove "a" we obtain the representation [("a",1);("b",1)].
a. (4 points) Given invariants, is the following code a correct implementation of the
equals function?
let equals (b1:’a bag) (b2:’a bag) : bool = b1 = b2
Circle YES or NO and briefly justify your answer.
Yes — the invariants guarantee that there is a unique list shape that represents a given
bag, so two bags are equal only if they are equal as lists.
b. (4 points) Consider the following code that implements the count operation for OPBag:
let rec count (x:’a) (b:’a bag) : int =
begin match b with
| [] -> 0
| (y,c)::ys ->
if x < y then 0 else (∗ <−−−−−− HERE ∗)
if x = y then c else
count x ys
end
Given the invariants, will the code still produce the correct output if the line marked
(∗ <−−−−−− HERE ∗) is deleted?
Circle YES or NO and briefly justify your answer.
It’s less efficient, but this will still find the appropriate count by traversing the whole
list and not finding x.
7
c. (4 points) Here is the code that implements the add operation for OPBag:
let rec add (x:’a) (b:’a bag) : ’a bag =
begin match b with
| [] -> [(x, 1)]
| (y,c)::ys ->
if x < y then (x,1)::b else (∗ <−−−−−− HERE ∗)
if x = y then (y,c+1)::ys else
(y,c)::add x ys
end
Given the invariants, will the code still produce the correct output if the line marked
(∗ <−−−−−− HERE ∗) is deleted?
Circle YES or NO and briefly justify your answer.
Removing this line violates invariant Inv2 — the resulting list may not be sorted.
d. (12 points) Complete the implementation of the remove operation for OPBag, properly
maintaining the invariants and using them to make the code as efficient as possible for
this representation:
let rec remove (x:’a) (b:’a bag) : ’a bag =
begin match b with
| [] -> []
| (y,c)::ys ->
if x < y then b else
if x = y then (if c = 1 then ys else (y, (c-1))::ys)
else (y,c)::(remove x ys)
end
8
5. Program Design: Recursion with Trees and Lists (29 points total)
This problem uses the definition of the ’a tree datatype given in the appendix, but we do
not assume that the trees satisfy the BST invariants.
Consider how to calculate the sum of the integers at each level of an int tree.
val level_sum : int tree -> int list
For example, given the tree t below, level_sum t computes the list [3; 4; 5; 7]. Here,
3 is the value at the root of the tree, 4 is the sum of integers at level 1, 5 is the sum of values
at level 2, and 7 is the sum of the (only) value at level 3. In general, the ith element of the
list is the sum of values at the ith level of the tree (starting at i = 0).
t : int tree =
Level 0:
-->
level_sum
3
Level 1:
-->
4
= (2 + 2)
-->
5
= (0 + 1 + 4)
-->
7
Level 2:
Level 3:
3
/ \
/
\
2
2
/ \
/
0
1 4
\
7
a. (4 points) When thinking about how to implement level_sum you created the following test code based on the example above. Fill in the blanks for the t_left and
t_right trees so that the tests succeed. We have completed two other tests for you.
let
let
let
let
leaf (n
t_left
t_right
t : int
: int) : int tree = Node(Empty, n, Empty)
: int tree = Node(leaf 0, 2, leaf 1)
: int tree = Node(Node(Empty, 4, leaf 7), 2, Empty)
tree = Node(t_left, 3, t_right)
let test () : bool = (level_sum Empty) = []
;; run_test "level_sum Empty" test
let test () : bool = (level_sum t_left) = [2; 1]
;; run_test "level_sum left subtree" test
let test () : bool = (level_sum t_right) = [2; 4; 7]
;; run_test "level_sum right subtree" test
let test () : bool = (level_sum t) = [3; 4; 5; 7]
;; run_test "example from diagram" test
9
Next, implement the function level_sum by decomposing the problem into two parts:
level_sum itself, and a helper function for combining the recursive results of level_sum.
Hint: Both of these functions are recursive.
b. (4 points) Complete the two test cases for helper given below. Note that the test cases
you completed in part a give the results of calling level_sum on the subtrees of t. We
have given you one additional test case to get you started. Hint: Understand the tests.
let test () : bool = (helper [1;2] [3;4]) = [4;6]
;; run_test "helper [1;2] [3;4]"
let test () : bool = (helper [] [7]) = [7]
;; run_test "helper [] [7]" test
let test () : bool =
(helper (level_sum t_left) (level_sum t_right)) = [4;5;7]
;; run_test "helper test 1" test
c. (12 points) Complete the code for this helper function:
let rec helper (lst1:int list) (lst2:int list) : int list =
begin match (lst1, lst2) with
| ([], _) -> lst2
| (_, []) -> lst1
| (x::xs, y::ys) -> (x + y) :: helper xs ys
end
d. (9 points) Finally, complete the implementation of level_sum, following the recursion
pattern for binary trees. Use the helper function as appropriate.
let rec level_sum (t : int tree) : int list =
begin match t with
| Empty -> []
| Node(lt, x, rt) ->
let lft = level_sum lt in
let rgt = level_sum rt in
x :: (helper lft rgt)
end
10
Scratch Space
Use this page if you run out of space elsewhere in the exam. Label your work here clearly and note
your use of scratch space at the problem in question.
11
Appendix: Binary Search Tree implementation
type ’a tree =
| Empty
| Node of ’a tree * ’a * ’a tree
let rec lookup (x: ’a) (t: ’a tree) : bool =
begin match t with
| Empty -> false
| Node (lt, v, rt) ->
if x < v then lookup x lt
else if x > v then lookup x rt
else true
end
let rec insert (x: ’a) (t: ’a tree) : ’a tree =
begin match t with
| Empty -> Node (Empty, x, Empty)
| Node (lt, v, rt) ->
if x < v then Node (insert x lt, v, rt)
else if x > v then Node (lt, v, insert x rt)
else t
end
let rec tree_max (t:’a tree) : ’a =
begin match t with
| Empty -> failwith "tree_max called on empty tree"
| Node(_,x,Empty) -> x
| Node(_,_,rt) -> tree_max rt
end
let rec delete (x:’a) (t:’a tree) : ’a tree =
begin match t with
| Empty -> Empty
| Node(lt,n,rt) ->
if x = n then
begin match (lt, rt) with
| (Empty, Empty) -> Empty
| (Empty, _) -> rt
| (_, Empty) -> lt
| (_,_)
->
let y = tree_max lt in
Node (delete y lt, y, rt)
end
else
if x < n then Node(delete x lt, n, rt)
else Node(lt, n, delete x rt)
end
12