Homework 2
Due: Sunday, September 21st at 11:59pm.
Submission Instructions
Start with the template code here.
- Replace each
failwith "TODO"
with your solution. (failwith
has typestring -> t
for any typet
, and throws an exception when it evaluates. Your solution to each problem should not includefailwith
.) - Be sure to test your solutions in
utop
or in tests alongside your submission. To load your file intoutop
, use#use "hw2.ml";;
. - Submit your version of
hw2.ml
to the HW2 assignment on Gradescope. Your version ofhw2.ml
should include (at least) the functions above, with the same types as above. - This assignment is partially autograded, which will give you immediate feedback about the correctness of your code via random testing. You may submit as many times as you would like. We will only manually grade the last submitted assignment. Please ensure your code type checks locally before you submit it (i.e., loading it into
utop
does not fail). - At the top of the template file, there is a comment for you to write in approximately how many hours you spent on the assignment. Filling this in is optional, but helpful feedback for the instructors.
Q1: Defining concat
(Autograded)
Using recursion, define the function concat : string list -> string
that concatenates a list of strings in order. The empty list should produce the empty string ""
.
Some example unit tests:
concat [] = ""
concat [""; "a"] = "a"
concat ["abc"; "def"] = "abcdef"
concat ["a"; ""; "b"; "cd"] = "abcd"
Q2: Testing a list: alternating
(Autograded)
Implement a function alternating : int list -> bool
that tests whether the list alternates between even and odd (beginning at even). Your function should return true
on the empty list, and false
on any list that begins with an odd number. (For checking if x
is even, you can test whether x mod 2
equals 0
.)
Some example unit tests which should pass of your implementation:
alternating [] = true
alternating [1] = false
alternating [4] = true
alternating [1;2;3] = false
alternating [2;3;4] = true
alternating [2;3] = true
alternating [2;2] = false
Note
You may find it useful to use mutual recursion, which defines two recursive functions in terms of each other. You do this by defining the first function using let rec
, and the others by and
. For example:
let rec f (x : int) : int =
if x <= 0 then 1 else g (x - 1) + 1
and g (x : int) : int =
x + f (x - 1)
f
calls g
and g
calls f
.
(If you have been writing ;;
at the end of your definitions, take note that this example won't work if we put ;;
between the definition of f
and g
.)
If f
and g
have the same input and outupt types, we can alternatively eliminate the mutual recursion by using a parameter that keeps track of which function we are:
(*
- which = true: we are behaving like `f`
- which = false: we are behaving like `g`
*)
let rec fg (which : bool) (x : int) : int =
if which then
(* Behave as f *)
if x <= 0 then 1 else fg false (x - 1) + 1
else
(* Behave as g *)
x + fg true (x - 1)
let f : int -> int = fg true
let g : int -> int = fg true
Q3: Generator, stringifier for int_tree
(Manually graded)
For this question and the rest of the HW, consider this data structure:
This is the type of a binary tree where leaves hold no data, while internal nodes hold anint
in addition to their subtrees.
First, define a generator (random sampler) for int_tree
:
This generator should create a tree of depth depth
(returning Leaf
if depth <= 0
, which counts as depth zero) and fill each node with an integer using Random.int
, with integer bound bound
. Your implementation should create trees that are balanced: for each node, the left subtree should have the same "shape" (number of nodes and branching structure) as the right subtree.
Next, define a stringifier for int_tree
:
string_of_int
will be useful here.
Q4: Mapping over an int_tree
(Autograded)
The map : (int -> int) -> int list -> int list
function for lists applies the argument function to each element of the list, creating a new list of the same length:
map (fun (x : int) -> x + 1) [1; 2; 3] = [2; 3; 4]
map (fun (x : int) -> x * 2) [] = []
map (fun (x : int) -> x * 2) [42] = [84]
In this problem, you will define a similar function for int_tree
:
f
to each int
in the original tree.
Some example tests:
map_int_tree (fun (x : int) -> x + 3) Leaf = Leaf
map_int_tree (fun (x : int) -> x + 3)
(Node (Node (Leaf, 0, Leaf), 3, Node (Leaf, 7, Leaf)))
=
(Node (Node (Leaf, 3, Leaf), 6, Node (Leaf, 10, Leaf)))
Q5: Composing int_tree_map
with itself (Manually graded)
Similar to what we did for lists in class, we can create a testing harness to test properties about int_tree
s:
let rec tester (prop : int_tree -> bool) (n : int) =
if n <= 0 then
print_endline "All tests succeeded"
else
let t = gen_int_tree 4 1000 in
if prop t then
tester prop (n - 1)
else
print_endline ("Found counterexample: " ^ string_of_int_tree t)
prop
on a random int_tree
a maximum of n
times in a row. If all tests succeed, then we have successfully tested prop
to always return true on our random inputs; if any test fails, we end and print the counter-example.
In this problem, define a logical specification that states that, given an int_tree
t
, mapping it using map_int_tree
twice in a row with the function (fun (x : int) -> x + 1)
is the same as mapping it once with the function (fun (x : int) -> x + 2)
.
This is a theorem that should be true about map_int_tree
. You can test the truthiness of your version of map_2_spec
by calling tester
against it; if the tester
finds a counter-example, there is likely an issue with your implementation of map_2_spec
or map_int_tree
.
Take note that if tester
says that all tests succeeded, this only means that your version of map_2_spec
is true; not that it is the correct one!
(We will not grade you on whether you called tester
on map_int_tree
, but doing so will help you find potential bugs.)
Q6: Maximum of int_tree
(Autograded)
Define a function int_tree_max : int_tree -> int option
that returns the maximum int
in the tree, if it exists.
Q7: Defining whether an int_tree
is sorted
(Manually graded)
An int_tree
is sorted if, for all nodes Node(x, i, y)
in the tree, i
is greater than or equal to all integers stored in x
and less than or equal to all integers stored in y
. For example, Node(Node(Leaf, 0, Leaf), 1, Leaf)
is sorted, but Node(Leaf, 1, Node(Leaf, 0, Leaf))
is not.
Implement a boolean test for sorted
:
int_tree_max
will be helpful here.
Q8: Testing a theorem about sorted
(Manually graded)
Implement the property sorted_plus1_spec
to state that given an int_tree
t
: if t
is sorted, then the tree obtained by mapping t
by fun (x : int) -> x + 1
(via map_int_tree
) is also sorted.
As in Q5, you can use tester
to test if sorted_plus1_spec
always holds.
Q9: Another theorem about sorted
(Manually graded)
Implement the property sorted_new_node_spec
to state that given an int_tree
t
and an integer i
: if t
is sorted and has a maximum Some v
(computed from int_tree_max
), and if i
is greater than or equal to v
, then Node(t, i, Leaf)
is also sorted.
As in Q5, you can use tester
to test if new_node_spec
always holds. (To use this one with tester
, you have to
create a suitable prop
of type int_tree -> bool
. This prop
will need to internally sample a random integer i
and call sorted_new_node_spec
using that i
.)