CS 61A Structure and Interpretation of Computer Programs
Fall 2014
INSTRUCTIONS
Midterm 2
• You have 2 hours to complete the exam.
• The exam is closed book, closed notes, closed computer, closed calculator, except one hand-written 8.5” ⇥ 11”
crib sheet of your own creation and the 2 o cial 61A midterm study guides attached to the back of this exam.
• Mark your answers ON THE EXAM ITSELF. If you are not sure of your answer you may wish to provide a brief explanation.
Last name
First name
SID
Login
TA & section time
Name of the person to your left
Name of the person to your right
All the work on this exam is my own. (please sign)
For sta↵ use only
Q.1 Q.2 Q.3 Q.4 Q.5 Total /12 /14 /8 /8 /8 /50
2
Blank Page
1. (12 points) Class Hierarchy
For each row below, write the output displayed by the interactive Python interpreter when the expression is evaluated. Expressions are evaluated in order, and expressions may a↵ect later expressions.
Whenever the interpreter would report an error, write Error. You should include any lines displayed before an error. Reminder: The interactive interpreter displays the repr string of the value of a successfully evaluated expression, unless it is None. Assume that you have started Python 3 and executed the following:
class Worker: greeting = ’Sir’
def __init__(self): self.elf = Worker
def work(self):
return self.greeting + ’, I work’
def __repr__(self):
return Bourgeoisie.greeting
class Bourgeoisie(Worker): greeting = ’Peon’
def work(self):
print(Worker.work(self))
return ’My job is to gather wealth’ class Proletariat(Worker):
greeting = ’Comrade’ def work(self, other):
other.greeting = self.greeting + ’ ’ + other.greeting other.work() # for revolution
return other
= Worker ()
= Bourgeoisie ()
jack
john
jack.greeting = ’Maam’
3
Expression
5*5 1/0
Interactive Output
25
Error
Expression
Interactive Output
john.work()[10:]
Proletariat().work(john)
john.elf.work(john)
Worker().work()
jack
jack.work()
4
2. (14 points) Space
(a) (8 pt) Fill in the environment diagram that results from executing the code below until the entire program is finished, an error occurs, or all frames are filled. You may not need to use all of the spaces or frames.
A complete answer will:
• Add all missing names and parent annotations to all local frames. • Add all missing values created during execution.
• Show the return value for each local frame.
1 2 3 4 5 6 7 8 9
10 11 12 13
func locals(only) [parent=Global]
f1: ___________ [parent=____________]
Return Value
f2: ___________ [parent=____________]
Return Value
f3: ___________ [parent=____________]
Return Value
Global frame
locals
only 3
def locals(only):
def get(out):
nonlocal only
def only(one):
return lambda get: out
out = out + 1
return [out + 2]
out = get(-only)
return only
only = 3
earth = locals(only)
earth(4)(5)
f4: ___________ [parent=____________]
Return Value
(b) (6 pt) Fill in the blanks with the shortest possible expressions that complete the code in a way that results in the environment diagram shown. You can use only brackets, commas, colons, and the names luke, spock, and yoda. You *cannot* use integer literals, such as 0, in your answer! You also cannot call any built-in functions or invoke any methods by name.
2
list list 00
21
5
spock, yoda = 1, 2
!
!
luke = [______________________________________________________________________] !
!
yoda = 0
!
!
yoda = [______________________________________________________________________] !
! yoda.append(__________________________________________________________________)
Global frame spock 1 luke
yoda
list 012
list list 0120
2
6
3. (8 points) This One Goes to Eleven
(a) (4 pt) Fill in the blanks of the implementation of sixty_ones below, a function that takes a Link instance
representing a sequence of integers and returns the number of times that 6 and 1 appear consecutively.
def sixty_ones(s):
“””Return the number of times that 1 directly follows 6 in linked list s.
>>> once = Link(4, Link(6, Link(1, Link(6, Link(0, Link(1))))))
>>> twice = Link(1, Link(6, Link(1, once)))
>>> thrice = Link(6, twice)
>>> apply_to_all(sixty_ones, [Link.empty, once, twice, thrice])
[0, 1, 2, 3]
“””
if _________________________________________________________________:
return 0
elif _______________________________________________________________:
return 1 + _____________________________________________________: else:
return _________________________________________________________
(b) (4 pt) Fill in the blanks of the implementation of no_eleven below, a function that returns a list of all
distinct length-n lists of ones and sixes in which 1 and 1 do not appear consecutively. def no_eleven(n):
“””Return a list of lists of 1’s and 6’s that do not contain 1 after 1.
>>> no_eleven (2)
[[6, 6], [6, 1], [1, 6]]
>>> no_eleven (3)
[[6, 6, 6], [6, 6, 1], [6, 1, 6], [1, 6, 6], [1, 6, 1]] >>> no_eleven (4)[:4] # first half
[[6, 6, 6, 6], [6, 6, 6, 1], [6, 6, 1, 6], [6, 1, 6, 6]] >>> no_eleven (4)[4:] # second half
[[6, 1, 6, 1], [1, 6, 6, 6], [1, 6, 6, 1], [1, 6, 1, 6]] “””
if n == 0:
return _____________________________________________________________ elif n == 1:
return else:
a, b = return
_____________________________________________________________
no_eleven(___________________), no_eleven(___________________) [_________________ for s in a] + [_________________ for s in b]
4. (8 points) Tree Time
(a) (4 pt) A GrootTree g is a binary tree that has an attribute parent. Its parent is the GrootTree in which g is a branch. If a GrootTree instance is not a branch of any other GrootTree instance, then its parent is BinaryTree.empty.
BinaryTree.empty should not have a parent attribute. Assume that every GrootTree instance is a branch of at most one other GrootTree instance and not a branch of any other kind of tree.
Fill in the blanks below so that the parent attribute is set correctly. You may not need to use all of the lines. Indentation is allowed. You should not include any assert statements. Using your solution, the doctests for fib_groot should pass. The BinaryTree class appears on your study guide.
Hint: A picture of fib_groot(3) appears on the next page.
class GrootTree(BinaryTree):
“””A binary tree with a parent.”””
def __init__(self, entry, left=BinaryTree.empty, right=BinaryTree.empty): BinaryTree.__init__(self, entry, left, right)
______________________________________________________________________
______________________________________________________________________
______________________________________________________________________
______________________________________________________________________
______________________________________________________________________
______________________________________________________________________
def fib_groot(n):
“””Return a Fibonacci GrootTree.
>>> t = fib_groot (3)
>>> t.entry 2
>>> t.parent.is_empty True
>>> t.left.parent.entry 2
>>> t.right.left.parent.right.parent.entry 1
“””
if n == 0 or n == 1:
return GrootTree(n) else:
left, right = fib_groot(n-2), fib_groot(n-1)
return GrootTree(left.entry + right.entry, left, right)
7
8
(b) (4 pt) Fill in the blanks of the implementation of paths, a function that takes two arguments: a GrootTree instance g and a list s. It returns the number of paths through g whose entries are the elements of s. A path through a GrootTree can extend either to a branch or its parent.
You may assume that the GrootTree class is implemented correctly and that the list s is non-empty.
The two paths that have entries [2, 1, 2, 1, 0] in fib_groot(3) are shown below (left). The one path that has entries [2, 1, 0, 1, 0] is shown below (right).
Two paths for [2, 1, 2, 1, 0] One path for [2, 1, 0, 1, 0]
222
111111
010101
def paths(g, s):
“””The number of paths through g with entries s.
>>> t = fib_groot (3)
>>> paths(t, [1]) 0
>>> paths(t, [2])
1
>>> paths(t, [2, 1, 2, 1, 0]) 2
>>> paths(t, [2, 1, 0, 1, 0]) 1
>>> paths(t, [2, 1, 2, 1, 2, 1]) 8
“””
if g is BinaryTree.empty ________________________________________________: return 0
elif ____________________________________________________________________: return 1
else:
xs = [_______________________________________________________________]
return sum([ ___________________________________________ for x in xs])
5. (8 points) Abstraction and Growth
(a) (6 pt) Your project partner has invented an abstract representation of a sequence called a slinky, which uses a transition function to compute each element from the previous element. A slinky explicitly stores only those elements that cannot be computed by calling transition, using a starts dictionary. Each entry in starts is a pair of an index key and an element value. See the doctests for examples.
Help your partner fix this implementation by crossing out as many lines as possible, but leaving a program that passes the doctests. Do not change the doctests. The program continues onto the following page.
def length(slinky): return slinky [0]
def starts(slinky): return slinky [1]
def transition(slinky): return slinky [2]
def slinky(elements , transition):
“””Return a slinky containing elements.
>>> t = slinky([2, 4, 10, 20, 40], lambda x: 2*x)
>>> starts(t)
{0: 2, 2: 10}
>>> get(t, 3)
20
>>> r = slinky(range(3, 10), lambda x: x+1)
>>> length(r) 7
>>> starts(r)
{0: 3}
>>> get(r, 2) 5
>>> slinky([], abs)
[0, {},
>>> slinky([5, 4, 3], abs)
[3, {0: 5, 1: 4, 2: 3},
starts = {}
last = None
for e in elements [1:]:
for index in range(len(elements)):
if not e:
if index == 0:
return [0, {}, transition]
if last is None or e != transition(last):
if e == 0 or e != transition(last):
if index == 0 or elements[index] != transition(elements[index-1]):
starts[index] = elements[index] starts[index] = elements.pop(index) starts[e] = transition(last) starts[e] = last
last = e
return return return return
[len(starts), starts , transition] [len(elements), starts , transition] [len(starts), elements , transition] [len(elements), elements , transition]
9
10
def get(slinky, index):
“””Return the element at index of slinky.””” if index in starts(slinky):
return starts(slinky)[index] start = index
start = 0
f = transition(slinky)
while start not in starts(slinky): while not f(get(start)) == index:
start = start + 1
start = start – 1
value = starts(slinky)[start] value = starts(slinky)[0] value = starts(slinky)[index] while start < index:
while value < index:
value = f(value) value = value + 1 start = start + 1 start = start + index
return value return f(value)
(b) (2 pt) Circle the ⇥ expression below that describes the number of operations required to compute slinky(elements, transition), assuming that
• n is the initial length of elements,
• d is the final length of the starts dictionary created,
• the transition function requires constant time,
• the pop method of a list requires constant time,
• the len function applied to a list requires linear time,
• the len function applied to a range requires constant time,
• adding or updating an entry in a dictionary requires constant time,
• getting an element from a list by its index requires constant time,
• creating a list requires time that is proportional to the length of the list.
⇥(1) ⇥(n) ⇥(d) ⇥(n2) ⇥(d2) ⇥(n · d)
Scratch Paper
11
12
Scratch Paper
CS 61A Midterm 1 Study Guide – Page 1
Import statement
Name
Value
Assignment statement
Code (left):
Statements and expressions
Red arrow points to next line.
Gray arrow points to the line just executed
Binding
Frames (right):
A name is bound to a value
In a frame, there is at most one binding per name
208
mul(add(2, mul(4, 6)), add(3, 5))
mul 26 8 add(2, mul(4, 6)) add(3, 5)
add 2 24 mul(4, 6)
mul 4 6
add 3 5
Pure Functions
-2 abs(number):
2
2, 10 pow(x, y): Non-Pure Functions
-2 print(...): display “-2”
1024
None
Intrinsic name of function called
Formal parameter bound to argument
Local frame
Return value is
not a binding!
User-defined function
Built-in function
Defining:
Formal parameter >>> def square( x ):
return mul(x, x)
(return statement) Call expression: square(2+2) operand: 2+2
argument: 4
operator: square function: func square(x)
Calling/Applying: 4 square( x ):
Argument Intrinsic name
return mul(x, x) 16 Return value
Compound statement
Clause
Suite
…
…
!
2 boolean contexts
def abs_value(x):
if x > 0: return x
elif x == 0: return 0
else:
return -x
1 statement, 3 clauses,
3 headers,
3 suites,
2
A name evaluates to
the value bound to
that name in the
earliest frame of
the current 1 environment in which
that name is found.
2
Error
1
•An environment is a sequence of frames
•An environment for a non-nested function (no def within def) consists of one local frame, followed by the global frame
Evaluation rule for call expressions:
1.Evaluate the operator and operand subexpressions. 2.Apply the function that is the value of the operator
subexpression to the arguments that are the values of the operand subexpressions.
Applying user-defined functions:
1.Create a new local frame with the same parent as the function that was applied.
2.Bind the arguments to the function’s formal parameter names in that frame.
3.Execute the body of the function in the environment beginning at that frame.
Execution rule for def statements:
1.Create a new function value with the specified name, formal parameters, and function body.
2.Its parent is the first frame of the current environment. 3.Bind the name of the function to the function value in the
first frame of the current environment.
Execution rule for assignment statements:
1.Evaluate the expression(s) on the right of the equal sign. 2.Simultaneously bind the names on the left to those values,
in the first frame of the current environment.
Execution rule for conditional statements:
Each clause is considered in order.
1.Evaluate the header’s expression.
2.If it is a true value, execute the suite, then skip the remaining clauses in the statement.
Evaluation rule for or expressions:
1.Evaluate the subexpression
2.If the result is a true value v, then the expression
evaluates to v.
3.Otherwise, the expression evaluates to the value of the
subexpression
Evaluation rule for and expressions:
1.Evaluate the subexpression
2.If the result is a false value v, then the expression
evaluates to v.
3.Otherwise, the expression evaluates to the value of the
subexpression
Evaluation rule for not expressions:
1.Evaluate
Execution rule for while statements:
1. Evaluate the header’s expression.
2. If it is a true value, execute the (whole) suite, then return to step 1.
B AB
A call expression and the body of the function being called are evaluated in different environments
1
A
2
1
def fib(n): hof.py
“””Compute the nth Fibonacci number, for N >= 1.”””
pred, curr = 0, 1 # Zeroth and first Fibonacci numbers
k = 1 # curr is the kth Fibonacci number return total
pred, curr = curr, pred + curr
k=k+1
def identity(k):
return curr return k
while k < n: def cube(k): return pow(k, 3) Function of a single argument (not called term) A formal parameter that will be bound to a function def summation(n, term): """Sum the first n terms of a sequence. >>> summation(5, cube)
225
“””
total, k = 0, 1 while k <= n: The cube function is passed as an argument value total, k = total + term(k), k + 1 return total The function bound to term gets called here 0+13 +23 +33 +43 +55 def pi_term(k): return 8 / (k * 4 − 3) / (k * 4 − 1) # Local function definitions; returning functions Def statement Body “y” is not found “y” is not found Return expression Higher-order function: A function that takes a function as an argument value or returns a function as a return value Nested def statements: Functions defined within other function bodies are bound to names in the local frame def make_adder(n): CS 61A Midterm 1 Study Guide – Page 2 square = lambda x,y: x * y Evaluates to a function. square = lambda x: x * x VS def square(x): return x * x • Both create a function with the same domain, range, and behavior. • Both functions have as their parent the environment in which they were defined. • Both bind that function to the name square. • Only the def statement gives the function an intrinsic name. When a function is defined: A function No "return" keyword! with formal parameters x and y that returns the value of "x * y" Must be a single expression Can refer to names in the enclosing function A function that returns a function def make_adder(n): """Return a function that takes one argument k and returns k + n. ! The name add_three is bound to a function 1. Create a function value: func
>>> add_three = make_adder(3)
>>> add_three(4) 7
“””
def adder(k): return k + n
return adder
A local def statement
3. Bind
(which is the first frame of the current environment).
When a function is called:
1. Add a local frame, titled with the
2. Copy the parent of the function to the local frame: [parent=