Lab 14: Final Review
Starter Files
Download lab14.zip. Inside the archive, you will find starter files for the questions in this lab, along with a copy of the OK autograder.
Linked Lists
Question 1: Remove All
Implement a function remove_all
that takes a Link
, and a value
,
and remove any list node containing that value. You can assume the
list already has at least one node containing value
and the first element is
never removed. Notice that you are not returning anything, so you should mutate the list.
def remove_all(link , value):
"""Remove all the nodes containing value. Assume there exists some
nodes to be removed and the first element is never removed.
>>> l1 = Link(0, Link(2, Link(2, Link(3, Link(1, Link(2, Link(3)))))))
>>> print_link(l1)
<0 2 2 3 1 2 3>
>>> remove_all(l1, 2)
>>> print_link(l1)
<0 3 1 3>
>>> remove_all(l1, 3)
>>> print_link(l1)
<0 1>
"""
"*** YOUR CODE HERE ***"
if link is Link.empty or link.rest is Link.empty:
return
if link.rest.first == value:
link.rest = link.rest.rest
remove_all(link, value)
else:
remove_all(link.rest, value)
# alternate solution
if link is not Link.empty and link.rest is not Link.empty:
remove_all(link.rest, value)
if link.rest.first == value:
link.rest = link.rest.rest
Use OK to test your code:
python3 ok -q remove_all
Trees
Question 2: Reverse Other
Write a function reverse_other
that mutates the tree such that every other
(odd_indexed) level of the tree's entries are all reversed. For example
Tree(1,[Tree(2), Tree(3)])
becomes Tree(1,[Tree(3), Tree(2)])
def reverse_other(t):
"""Reverse the entries of every other level of the tree using mutation.
>>> t = Tree(1, [Tree(2), Tree(3), Tree(4)])
>>> reverse_other(t)
>>> t
Tree(1, [Tree(4), Tree(3), Tree(2)])
>>> t = Tree(1, [Tree(2, [Tree(5, [Tree(7), Tree(8)]), Tree(6)]), Tree(3)])
>>> reverse_other(t)
>>> t
Tree(1, [Tree(3, [Tree(5, [Tree(8), Tree(7)]), Tree(6)]), Tree(2)])
"""
"*** YOUR CODE HERE ***"
def reverse_helper(t, need_reverse):
if t.is_leaf():
return
new_labs = [child.root for child in t.branches][::-1]
for i in range(len(t.branches)):
child = t.branches[i]
reverse_helper(child, not need_reverse)
if need_reverse:
child.root = new_labs[i]
reverse_helper(t, True)
Use OK to test your code:
python3 ok -q reverse_other
Object-Oriented Programming
Question 3: Mint
Complete the Mint
and Coin
classes so that the coins created by a mint have
the correct year and worth.
- Each
Mint
instance has ayear
stamp. Theupdate
method sets theyear
stamp to thecurrent_year
class attribute of theMint
class. - The
create
method takes a subclass ofCoin
and returns an instance of that class stamped with themint
's year (which may be different fromMint.current_year
if it has not been updated.) - A
Coin
'sworth
method returns thecents
value of the coin plus one extra cent for each year of age beyond 50. A coin's age can be determined by subtracting the coin's year from thecurrent_year
class attribute of theMint
class.
class Mint:
"""A mint creates coins by stamping on years.
The update method sets the mint's stamp to Mint.current_year.
>>> mint = Mint()
>>> mint.year
2017
>>> dime = mint.create(Dime)
>>> dime.year
2017
>>> Mint.current_year = 2100 # Time passes
>>> nickel = mint.create(Nickel)
>>> nickel.year # The mint has not updated its stamp yet
2017
>>> nickel.worth() # 5 cents + (85 - 50 years)
38
>>> mint.update() # The mint's year is updated to 2100
>>> Mint.current_year = 2175 # More time passes
>>> mint.create(Dime).worth() # 10 cents + (75 - 50 years)
35
>>> Mint().create(Dime).worth() # A new mint has the current year
10
>>> dime.worth() # 10 cents + (160 - 50 years)
118
>>> Dime.cents = 20 # Upgrade all dimes!
>>> dime.worth() # 20 cents + (160 - 50 years)
128
>>> m = Mint()
>>> n = m.create(Nickel)
>>> n.worth()
5
>>> n.year = 2017
>>> n.worth()
113
"""
current_year = 2017
def __init__(self):
self.update()
def create(self, kind):
"*** YOUR CODE HERE ***"
return kind(self.year)
def update(self):
"*** YOUR CODE HERE ***"
self.year = Mint.current_year
class Coin:
def __init__(self, year):
self.year = year
def worth(self):
"*** YOUR CODE HERE ***"
return self.cents + max(0, Mint.current_year - self.year - 50)
class Nickel(Coin):
cents = 5
class Dime(Coin):
cents = 10
Use OK to test your code:
python3 ok -q Mint
Scheme
Scheme questions are to be done in lab14.scm
.
Question 4: Accumulate
Fill in the definition for the procedure accumulate
, which combines the first
n
natural numbers according to the following parameters:
combiner
: a function of two argumentsstart
: a number with which to start combiningn
: the number of natural numbers to combineterm
: a function of one argument that computes the nth term of a sequence
For example, we can find the product of all the numbers from 1 to 5 by using the multiplication operator as the combiner
, and starting our product at 1:
scm> (define (identity x) x)
scm> (accumulate * 1 5 identity) ; 1 * 1 * 2 * 3 * 4 * 5
120
We can also find the sum of the squares of the same numbers by using the addition operator as the combiner
and square
as the term
:
scm> (define (square x) (* x x))
scm> (accumulate + 0 5 square) ; 0 + 1^2 + 2^2 + 3^2 + 4^2 + 5^2
55
(define (accumulate combiner start n term)
(if (= n 0)
start
'YOUR-CODE-HERE
(combiner (accumulate combiner
start
(- n 1)
term)
(term n)) )
)
;;; Tests
(define (identity x) x)
(accumulate * 1 5 identity)
; expect 120
(define (square x) (* x x))
(accumulate + 0 5 square)
; expect 55
Use OK to test your code:
python3 ok -q accumulate
Question 5: How Many Dots?
Implement how-many-dots
, which takes in a nested scheme list s
and returns
the number of dots that appear when it is displayed by the interpreter (not
counting decimal points). You may assume that s
is a nested list that
contains only numbers.
Hints: A dot appears when the second element of a pair is not a well formed
list. The procedures pair?
, null?
, and number?
test whether a value is a pair, nil
, or a number, respectively.
(define (how-many-dots s)
'YOUR-CODE-HERE
(if (not (pair? s))
0
(+ (if (and (not (pair? (cdr s)))
(not (null? (cdr s))))
1
0)
(how-many-dots (car s))
(how-many-dots (cdr s)))))
;;; Tests
(how-many-dots '(1 2 3))
; expect 0
(how-many-dots '(1 2 . 3))
; expect 1
(how-many-dots '((1 . 2) 3 . 4))
; expect 2
(how-many-dots '((((((1 . 2) . 3) . 4) . 5) . 6) . 7))
; expect 6
(how-many-dots '(1 . (2 . (3 . (4 . (5 . (6 . (7))))))))
; expect 0
Use OK to test your code:
python3 ok -q how-many-dots
Question 6: Swap
Write a tail-recursive function swap
, which takes in a Scheme list s
and returns a new Scheme list where every two elements in s
are swapped.
(define (swap s)
'YOUR-CODE-HERE
(define (helper sofar rest)
(cond ((null? rest) sofar)
((null? (cdr rest)) (append sofar (list (car rest))))
(else (helper (append sofar (list (car (cdr rest)) (car rest)))
(cdr (cdr rest))))))
(helper () s))
;;; Tests
(swap (list 1 2 3 4))
; expect (2 1 4 3)
(swap (list 1 2 3 4 5))
; expect (2 1 4 3 5)
Use OK to test your code:
python3 ok -q swap
Interpreters
Question 7: Eval/Apply
This question is from the final review.With your Project 4 implementation, how many total calls to scheme_eval
and scheme_apply
would be made when evaluating the following two
expressions? Assume that you are not using the tail call optimized
scheme_eval_optimized
function.
(define (square x) (* x x))
(+ (square 3) (- 3 2))
14 eval calls, 4 apply calls
Iterators and Generators
Question 8: Str
Write an iterator that takes a string as input and outputs the letters in order when iterated over.
class Str:
def __init__(self, str):
self.str = str
def __iter__(self):
return iter(self.str)
That works (why?), but just kidding.
class Str:
"""
>>> s = Str("hello")
>>> for char in s:
... print(char)
...
h
e
l
l
o
>>> for char in s: # a standard iterator does not restart
... print(char)
"""
"*** YOUR CODE HERE ***"
def __init__(self, str):
self.str = str
self.i = -1
def __iter__(self):
return self
def __next__(self):
self.i += 1
if self.i >= len(self.str):
raise StopIteration
return self.str[self.i]
Use OK to test your code:
python3 ok -q Str
Question 9: List-Dictionary Iterator
Create an ListDictIter
iterator class that is initialized with a list and dictionary. Calling next on an instance of the class will return x
. x
is the dictionary value of the next item of the list, or, if the item is not a key in the dictionary, x
is just the item.
Hint: you can use the get method of a dictionary to specify a default value if a key is not in the dictionary, e.g. dct.get("dog", "cat")
will return either the value that "dog" is a key for in dct, or "cat" if "dog" is not a key.
class ListDictIter:
""" Returns an iterator that yields either the corresponding
dictionary value of the next element in the list or the element
itself if it is not a key in the dictionary.
>>> sounds = {'cat': 'meow', 'cow': 'moo','ghost': 'boo'}
>>> things = ['cat', 'moo', 'ghost', 'pikachu']
>>> x = ListDictIter(things, sounds)
>>> list(x)
['meow', 'moo', 'boo', 'pikachu']
>>> y = ListDictIter(things, sounds)
>>> next(y)
'meow'
>>> next(y)
'moo'
"""
def __init__(self, lst, dct):
"*** YOUR CODE HERE ***"
self.lst = lst
self.dct = dct
self.index = 0
def __iter__(self):
return self
def __next__(self):
"*** YOUR CODE HERE ***"
if self.index >= len(self.lst):
raise StopIteration
else:
m = self.index
self.index += 1
return self.dct.get(self.lst[m], self.lst[m])
Use OK to test your code:
python3 ok -q ListDictIter
Question 10: Pairs (generator)
Write a generator function pairs
that takes a list and yields all the
possible pairs of elements from that list.
def pairs(lst):
"""
>>> type(pairs([3, 4, 5]))
<class 'generator'>
>>> for x, y in pairs([3, 4, 5]):
... print(x, y)
...
3 3
3 4
3 5
4 3
4 4
4 5
5 3
5 4
5 5
"""
"*** YOUR CODE HERE ***"
for i in lst:
for j in lst:
yield i, j
Use OK to test your code:
python3 ok -q pairs
Question 11: Pairs (iterator)
Now write an iterator that does the same thing. You are only allowed to use a linear amount of space - so computing a list of all of the possible pairs is not a valid answer. Notice how much harder it is - this is why generators are useful.
class PairsIterator:
"""
>>> for x, y in PairsIterator([3, 4, 5]):
... print(x, y)
...
3 3
3 4
3 5
4 3
4 4
4 5
5 3
5 4
5 5
"""
def __init__(self, lst):
"*** YOUR CODE HERE ***"
self.lst = lst
self.i = 0
self.j = 0
def __next__(self):
"*** YOUR CODE HERE ***"
if self.i == len(self.lst):
raise StopIteration
result = (self.lst[self.i], self.lst[self.j])
if self.j == len(self.lst) - 1:
self.i += 1
self.j = 0
else:
self.j += 1
return result
def __iter__(self):
"*** YOUR CODE HERE ***"
return self
Use OK to test your code:
python3 ok -q PairsIterator
Streams
Question 12: Cycle
Define a function cycle
which takes in as input an ordinary Python
list and returns a stream which just repeats that list over and over
For example, cycle(['a', 'b', 'c'])
is the stream containing elements
['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', ...]
. If the input list
is empty, output the empty stream; otherwise, the output stream should
be infinite.
def cycle(list):
"""
Returns a Stream that cycles through each entry of the list.
>>> s = cycle(['a', 'b', 'c'])
>>> s.first
'a'
>>> s.rest.first
'b'
>>> s.rest.rest.rest.rest.first
'b'
>>> t = cycle([])
>>> t is Stream.empty
True
>>> isinstance(s, Stream)
True
>>> isinstance(s.rest.rest, Stream)
True
"""
"*** YOUR CODE HERE ***"
if list == []:
return Stream.empty
return Stream(list[0], lambda: cycle(list[1:] + list[:1]))
Use OK to test your code:
python3 ok -q cycle
Question 13: Run-Length Encoding
Run-length encoding is a very simple data compression technique, whereby runs of data are compressed and stored as a single value. A run is defined to be a contiguous sequence of the same number. For example, in the (finite) sequence
1, 1, 1, 1, 1, 6, 6, 6, 6, 2, 5, 5, 5
there are four runs: one each of 1, 6, 2, and 5. We can represent the same sequence as a sequence of tuples:
(5, 1), (4, 6), (1, 2), (3, 5)
Notice that the first element of each tuple is the number of times a particular number appears in a run, and the second element is the number in the run.
We will extend this idea to (possibly infinite) streams. Write a
function called rle
that takes in a stream of data, and returns a
corresponding stream of tuples, which represents the run-length encoded
version of the stream. It will also take in the maximum size of any
given run (default 10) to prevent having to compress infinite runs.
def rle(s, max_run_length=10):
"""
>>> example_stream = lst_to_stream([1, 1, 1, 2, 3, 3])
>>> encoded_example = rle(example_stream)
>>> stream_to_list(encoded_example, 3)
[(3, 1), (1, 2), (2, 3)]
>>> shorter_encoded_example = rle(example_stream, 2)
>>> stream_to_list(shorter_encoded_example, 4)
[(2, 1), (1, 1), (1, 2), (2, 3)]
>>> ints = Stream(1, lambda: increment_stream(ints))
>>> encoded_naturals = rle(ints)
>>> stream_to_list(encoded_naturals, 3)
[(1, 1), (1, 2), (1, 3)]
>>> ones = Stream(1, lambda: ones)
>>> encoded_ones = rle(ones, max_run_length=3)
>>> stream_to_list(encoded_ones, 3)
[(3, 1), (3, 1), (3, 1)]
"""
"*** YOUR CODE HERE ***"
if s == Stream.empty:
return s
first, s, count = s.first, s.rest, 1
while s != Stream.empty and s.first == first and count < max_run_length:
s, count = s.rest, count+1
return Stream((count, first), lambda: rle(s, max_run_length))
Use OK to test your code:
python3 ok -q rle