CSE 130, Fall 2011 Name/ID Instructor: Ranjit Jhala
Final Exam
Instructions: read these first!
Do not open the exam, turn it over, or look inside until you are told to begin. Switch off cell phones and other potentially noisy devices.
Write your full name on the line at the top of this page. Do not separate pages.
You may refer to your single-page (double-sided) cheat sheet, but no compu- tational devices (such as laptops, calculators, phones, friends, enemies, pets, lovers).
Read questions carefully. Show all work you can in the space provided. Where limits are given, write no more than the amount specified.
The rest will be ignored.
Avoid seeing anyone elses work or allowing yours to be seen.
Do not communicate with anyone but an exam proctor.
If you have a question, raise your hand.
When time is up, stop writing.
The points for each part are rough indication of the time that part should take.
Run LATEX again to produce the table
CSE 130, Fall 2011 Final Exam Page 1 of ?? 1. [?? points] A dictionary is a data structure that maps (string) keys to values. We will represent dictionaries using a
polymorphic Ocaml datatype:
type a dict = Empty | Node of string * a * a dict * a dict
That is, a dictionary is represented as a tree, which is either empty, or a node with:
1. a binding from a string key to an a value, 2. a left sub-dictionary, and
3. a right sub-dictionary.
For example, consider the dictionary
fruit price
apple 2.25 banana 1.50 cherry 2.75 grape 2.65 kiwi 3.99 orange 0.75 peach 1.99
that represents the prices (per pound) of various fruits. This dictionary is represented by the tree (on the left) which in turnisrepresentedbytheOcamlvalue(oftypefloat dict)boundtofruitdontheright.
grape:
_____2.65_____
|| banana: orange:
___1.50___ ___0.75___ ||||
apple:cherry:kiwi:peach:
2.252.75 3.99 1.99
let fruitd =
Node (grape, 2.65,
Node (banana, 1.50,
Node (apple, 2.25, Empty, Empty),
Node (cherry, 2.75, Empty, Empty)),
Node (orange, 0.75,
Node (kiwi, 3.99, Empty, Empty),
Node (peach, 1.99, Empty, Empty)))
Notice the tree is Binary-Search-Ordered, meaning for each node with a key k,
keys in the left subtree are (lexicographically) less than k, and keys in the right subtree are (lexicographically) greater than k.
CSE 130, Fall 2011 Final Exam Page 2 of ??
(a) [5 points] The function
val find: a dict -> string -> a
should be defined so that find d k evaluates to the value associated with the key k in the dictionary d. For examplefind fruitd cherryevaluatesto2.75.
Fill in the blanks below to implement find as described.
let rec find d k =
match d with
| Empty ->
raise Not_found
| Node (k, v, l, r) ->
if k = k then else
if k < k then else(* k > k *)
(b) [10 points] The function
val add: a dict -> string -> a -> a dict
should be defined so that add d k v gives a new dictionary that results from adding the key-value pair (k,v) to dictionary d. For example the expression
let d0 = fruitd in
let d1 = add d0 banana 5.0 in
let d2 = add d1 mango 10.25 in
(find d2 banana, find d2 mango, find d2 cherry)
shouldevaluateto(5.0, 10.25, 2.75).
Fill in the blanks below to implement add as described.
let rec add d k v =
match d with
| Empty ->
| Node (k, v, l, r) ->
if k = k then else
if k < k then else (* k > k *)
CSE 130, Fall 2011 Final Exam Page 3 of ??
2. [?? points] MapReduce is a software framework introduced by Google to support distributed computing on large data sets on clusters of computers. The framework is inspired by map and reduce functions commonly used in functional programming. (From WikiPedia)
This question will give you a flavor of what it is like to program in the MapReduce model, using a simple Ocaml implementation that runs on a single machine.
(a) [5 points] Consider the function expand whose type is given at the bottom. let rec expand f xs =
match xs with
| []-> []
| x::xs-> (f x) @ (expand f xs)
val expand : (a -> b list) -> a list -> b list
What does the following expression evaluate to:
let rec clone (x,n) = if n <= 0 then [] else x::(clone (x, n-1)) in expand clone [(“a”,1); (“b”, 2); (“c”, 3)](b) [5 points] Consider the function insert whose type is given at the bottom. let rec insert t (key,value) = match t with | []-> [(key,[value])]
| (k,vs)::t-> if k = key
then (k, value::vs)::t
else (k, vs)::(insert t (key,value))
val insert: (a * b list) list -> (a * b) -> (a * b list) list
What does the following expression evaluate to:
let t = [(judynails,[2]); (larsumlaut,[2;2;9]); (caseylynch,[3])] in
let kv= (judynails,4) in
insert t kv
(c) [5 points] Consider the function group whose type is given at the bottom. let group kvs = List.fold_left insert [] kvs
val group : (a * b) list -> (a * b list) list
What does the following expression evaluate to:
let kvs = [(judynails, 3); (larsumlaut, 8); (caseylynch, 19);
(caseylynch, 12); (larsumlaut, 7); (judynails, 6)] in
group kvs
Result:
Result:
CSE 130, Fall 2011 Final Exam Page 4 of ??
Result:
(d) [5 points] Consider the function collapse whose type is given at the bottom.
let collapse f gs = List.map (fun (k,v::vs) -> (k, List.fold_left f v vs)) gs
val collapse: (a -> a -> a) -> (b * a list) list -> (b * a) list
What does the following expression evaluate to:
let gs = [(judynails,[9;3]); (larsumlaut,[5;2;3]); (caseylynch,[3;6])] in
collapse (fun x y -> x + y) gs
(e) [10 points] Finally, consider the function map_reduce whose type is given at the bottom. let map_reduce xs mapper reducer =
let kvs = expand mapper xs in
let gs= group kvs in
let rs= collapse reducer gs in
rs
val map_reduce: a list -> (a->(b *c) list) -> (c->c->c) -> (b *c) list Intuitively, the map_reduce function takes the arguments:
xs: A list of values of type a, typically a list of documents,
mapper: A function that maps a a value to a list of key-value pairs kvs of type b * c list,
reducer: An accumulation function that takes a current accumulation value of type c a next value of
type c and returns a new accumulated value of type c.
The map_reduce function works as follows. First, it uses mapper to expand the list xs into a giant list of key-value pairs kvs. Second, it groups the key-value pairs using the keys to get a list gs. Third, it uses reducer to reduces the list of values in each group (indexed by k) using reducer into a single value that is indexed by k). In the real implementation, each of the three steps of map_reduce is carried out in parallel across several (thousands of!) machines. Assume that you are given
val words_of_document: document -> string list
val wwwdocs: document list
that is a function that returns a list of strings corresponding to the words in a given document, and the list of all documents on the Web. Suppose that you wish to compute the frequency with which different words appear in documents on the Web. That is you want to compute a list [(w1,c1);(w2,c2);;(wn,cn)] where ci is the number of times the word wi appears in documents across the Web. Fill in the blanks below to show how map_reduce. can be used to compute the word frequency.
let wordcount =
let fmap = in let fred = in
Result:
CSE 130, Fall 2011 Final Exam Page 5 of ?? map_reduce wwwdocs fmap fred
CSE 130, Fall 2011 Final Exam Page 6 of ?? 3. [?? points] In this problem, we will represent Python-style namespaces using Ocaml data structures. Consider the
following datatype declaration:
type name_space = EmptyNameSpace
| Info of (string * value) list * name_space
andvalue= Int of int
| Method of (name_space -> int -> int)
A name space is either the empty name space, or it contains some information. The information it contains is a list of string-to-value bindings, along with a pointer to the parent name space. A value is either an int, or it is a method. A method takes a name space as the first parameter (the self pointer), and an additional integer, and returns an integer.
Suppose we had the following Python code:
class SimpleObj1: a=0
def f(self, i): return i+1
class SimpleObj2 (SimpleObj1):
def g(self, i): return i+2
SimpleObj2()
The object created by the call to SimpleObj2() would be represented in our OCaml data structures as follows: let method_f self i = i+1
let SimpleObj1 = Info([(a, Int(0)); (f, Method(method_f))], EmptyNameSpace)
let method_g self i = i+2
let SimpleObj2 = Info([(g, Method(method_g))], SimpleObj1)
(a) [10 points] Write an OCaml function lookup: name_space -> string -> value that takes a name space and a name, and searches the name space (and parent names spaces) for the given name. If a value is found, then the value should be returned. If no value is found, you should raise NotFound. For example, if you run lookup SimpleObj2 ayoushouldgetInt(0)back,andifyourunlookup SimpleObj2 midori you will get an exception NotFound. Write your lookup function below:
CSE 130, Fall 2011 Final Exam Page 7 of ??
(b) [10 points] We will now see how to use the lookup function. First, consider the following simple conversion functions:
exception TypeError
let to_int value =
match value with
| Int(i) -> i
| _-> raise TypeError
let to_method value =
match value with
| Method(m) -> m
|_ -> raise TypeError
And consider the following Python code:
class SimpleObj3:
a = 10;
def f(self, i): return self.a + i
OBJ3 = SimpleObj3()
Fill in the OCaml code below so that the object created by SimpleObj3() above is represented in OCaml in the OBJ3 variable below (recall that self is a namespace!):
let method_f self i = (to_int (lookup + i;;
let OBJ3 = Info([(a, Int(10)); (f, Method(method_f))], EmptyNameSpace)
(c) [10 points] Finally, we will write an OCaml function that performs dynamic dispatch. In particular, fill in the codebelowforthefunctioninvoke_method: name_space -> string -> int -> int,whichtakes as parameters a name space (in other words an object), a method name, an integer, and returns the result of applying that method name to the given object with the integer parameter:
let invoke_method self name i = (to_method (lookup ))
Now fill in the parameters to the invoke_method function below so that it performs the Python dispatch OBJ3.f(3):
invoke_method
CSE 130, Fall 2011 Final Exam Page 8 of ?? 4. [?? points] Consider the following Python class definition.
class Iter(object):
def __init__(self, seed, ticker, value, finish):
self.ticker = ticker
self.finish = finish
self.value= value
self.x= seed
def next(self):
if self.finish(self.x): raise StopIteration
v= self.value(self.x)
self.x = self.ticker(self.x)
return v
def __iter__(self):
return self
Recall that
for x in y: c
is just a pretty way of writing
temp = y.__iter__()
while(True):
try:
x = temp.next()
except StopIteration:
break
c
(a) [10 points] Fill in the blanks below, such that the resulting code executes without any errors. That is, such that after the for-loop finishes, the value of z1 is [0,1,2,3,4,5,6,7,8,9,10].
s1 =
def t1(x): return
def v1(x): return
def f1(x): return
z1 = []
for x in Iter(s1,t1,v1,f1): z1.append(x)
assert(z1 == [0,1,2,3,4,5,6,7,8,9,10])
CSE 130, Fall 2011 Final Exam Page 9 of ?? (b) [15 points] Fill in the blanks below, such that the resulting code executes without any errors. That is, such that
after the for-loop finishes, the value of z2 is [1,1,2,3,5,8,13,21,34,55,89]. s2 =
def t2(x): return
def v2(x): return
def f2(x): return
z2 = []
for x in Iter(s2,t2,v2,f2): z2.append(x)
assert(z2 == [1,1,2,3,5,8,13,21,34,55,89])
(c) [10 points] Write a decorator print_first_k_args that takes a parameter k, and decorates a function by printing, for each call to the function, the first k arguments (or all arguments if the function takes less than k arguments), as well as the return value, as illustrated below (left). Write the print_first_k_args in the blanks below (hint: str(x) returns the string representation of x)
@print_first_k_args(1)
def sum(a,b): return a + b
>>> sum(3,4)
Arg 1: 3
Return: 7
7
@print_first_k_args(2)
def sum(a,b): return a + b
>>> sum(3,4)
Arg 1: 3
Arg 2: 4
Return: 7
7
@print_first_k_args(1)
def fac(n):
if n <= 1: return 1 else: return n*fac(n-1) >>> fac(3)
Arg 1: 3
Arg 1: 2
Arg 1: 1
Return: 1
Return: 2
Return: 6
6
CSE 130, Fall 2011 Final Exam Page 10 of ??
5. [?? points] For this problem, you will write Prolog code that checks whether a given ML expression is well-scoped, that is, that every variable that is used in the expression is bound in the expression. That is, your prolog code will check, just by looking at the code, not by running it, whether or not your nanoML implementation would have thrown a Nano.MLFailure Variable not bound: exception.
First, we shall encode nanoML expressions as Prolog terms via the following grammar.
expr ::=
| const(i)
| var(x)
| plus(expr,expr)
| leq(expr,expr)
| ite(expr,expr)
| letin(var(x),expr,expr) | fun(var(x),expr)
| app(expr,expr)
The table below shows several examples of Ocaml expressions, the Prolog term encoding that expression.
ML Expression
Prolog Expression Term
2
const(2)
x
var(x)
2+3
plus(const(2),const(3))
2 <= 3 leq(const(2),const(3))fun x -> x <= 4 fun(var(x),leq(var(x),const(4)))fun x -> fun y ->
if x then y else 0
fun(var(x),fun(var(y),
ite(var(x),var(y),const(0))))
let x = 10 in x
letin(var(x),const(10),var(x))
fun x ->
let y = x in
y+y
fun(var(x),
letin(var(y),var(x)
plus(var(y),var(y))))
(a) [10points] WriteaPrologpredicatereads(E,X)thatistrueifXisreadanywhereinsidetheexpressionE.When you are done, you should get the following behavior:
?- reads(plus(const(2),const(3)), x).
False.
?- reads(letin(var(x),const(1),var(a)), X). X=a
True.
?- reads(fun(var(x),plus(var(a),var(b))), X).
X = a;
X = b; True.
?- reads(fun(var(b),plus(var(a),var(b))), X).
X = a;
X = b; True.
CSE 130, Fall 2011 Final Exam Page 11 of ?? Write your solution by filling in the grid below. Hint: If you need an Or, you may add extra rules where needed,
(or better, just use the ; operator.)
reads(const(I), X) :- 0 = 1. % i.e. false
reads(var(X), Y) :-
reads(plus(E1,E2), X) :-
reads(leq(E1,E2), X) :-
reads(ite(E1,E2,E3), X) :-
reads(letin(var(Y),E1,E2), X) :-
reads(fun(var(Y),E), X) :-
reads(app(E1,E2), X) :-
(b) [15 points] Write a Prolog predicate wellscoped(E) that is true if E is well-scoped, that is, each variable that is read is previously bound. When you are done, you should get the following behavior:
?- wellscoped(plus(var(a),const(3))).
False.
?- wellscoped(letin(var(a),const(1),plus(var(a),const(3)))).
True.
?- wellscoped(fun(var(b),plus(var(a),var(b)))).
False.
?- wellscoped(fun(var(b),fun(var(a), plus(var(a),var(b))))).
True.
?- wellscoped(app(fun(var(a),plus(var(a),const(1))), var(a))).
CSE 130, Fall 2011 Final Exam Page 12 of ?? False.
?- wellscoped(app(fun(var(a),plus(var(a),const(1))),
letin(var(a),const(1), var(a)))).
True.
Todefinewellscoped,writeahelperpredicatehelper(E, Xs)whichistrueifeveryvariablethatisreadin E either occurs in Xs or occurs bound inside E. With this, you can define wellscoped as:
wellscoped(E) :- helper(E, []).
Write your definition for helper by filling in the grid below.
Hint: You need not use reads. You may use the built-in predicate member(X,Ys) which returns true if the atom X appears in the list Ys.
helper(const(I), Xs) :- 0 = 0. % i.e. true
helper(var(X), Xs) :-
helper(plus(E1,E2), Xs) :-
helper(leq(E1,E2), Xs) :-
helper(ite(E1,E2,E3), Xs) :-
helper(letin(var(Y),E1,E2), Xs) :-
helper(fun(var(Y),E), Xs) :-
helper(app(E1,E2), Xs) :-
Reviews
There are no reviews yet.