The exam is cumulative (as is the course itself), but with an emphasis on topics we have covered since the midterm.
Topics since midterm:
if __name__ == "__main__"
)sys.argv
)The exam will NOT include material that was in the readings but that we did not discuss in class, or use in our labs, or practice in the problem sets.
Rewrite the following function to improve the style: Show answer.
def longwinded(a, b, c):
if a > b:
if a > c:
return True
else:
return False
else:
return False
def longwinded(a, b, c):
return (a > b) and (a > c)
If we are returning boolean values from within a conditional expression, it is much clearer to return the boolean expression directly, so we don’t need to both figure out the conditional and the mapping between that conditional and the return value. Similarly, we want to avoid comparing boolean values to True
or False
with ==
, e.g.,
value = True
# Undesired
if value == True:
# Do something...
# Better
if value:
# Do something
At best comparing booleans is redundant, but it also doesn’t take into account other “truthy” or “falsy” values, e.g., None
, that we could want to use in a conditional, but are not strictly True
or False
.
What does the following function do (in one sentence) assuming x
is a list: Show answer.
def mystery(x):
if len(x) <= 1:
return True
else:
if x[0] < x[1]:
return False
else:
return mystery(x[1:])
mystery
returns True if the list is in descending sorted order.
Draw the memory model, as would be generated by Python Tutor/Visualizer (pythontutor.com), after the following code executes: Show answer.
x = [[1, 2], 3]
y = x[:] + [3]*2
x[1] = 5
Write a function that takes two parameters: a dictionary and a number. The function should update the dictionary by adding the number to each value in the dictionary. Show answer.
def add_num(a_dict, number):
for key in a_dict:
a_dict[key] += number
Recall that for key in a_dict:
is the same as for key in a_dict.keys():
.
We don’t need to return the dictionary. Instead this function modifies its
arguments, i.e., it modifies the dictionary provided as the argument:
>>> a = { 1: 2 }
>>> add_num(a, 10)
>>> a
{1: 12}
Which of the following statements evaluates to True? Show answer.
['a', 'b', 'c'] == list("abc")
[1, 2, 3] == list(range(1, 4))
['a', 'b', 'c'] == list({ 'a', 'b', 'c' })
[1, 2, 3] == list({ 'a': 1, 'b': 2, 'c': 3 }.values())
All of the above inputs (string, range, set and dictionary) to the list
function are iterables, and thus we can create lists from the elements, use
the types as loop sequences, etc..
['a', 'b', 'c'] == list("abc")
is True[1, 2, 3] == list(range(1, 4))
is True['a', 'b', 'c'] == list({ 'a', 'b', 'c' })
may or may not be True. We
can’t predict just by looking at the code at the iteration order of the
values of a set (i.e. we treat sets as unordered).[1, 2, 3] == list({ 'a': 1, 'b': 2, 'c': 3 }.values())
is True.
Historically we could not predict the iteration order of the items in a
dictionary. However, as of Python 3.7, dictionaries now maintain
insertion order (that is when you iterate the keys/values are in the order you inserted them into the dictionary).Write a function named strip_upper
that takes a string as a parameter and
returns the string with all the uppercase letters removed. Recall that the string class has an
isupper
method that checks if it is all uppercase. Show answer.
def strip_upper(a_string):
result = ""
for char in a_string:
if not char.isupper():
result += char
return result
A recursive implementation could be:
def strip_upper(a_string):
if a_string == "":
return ""
else:
if a_string[0].isupper():
return strip_upper(a_string[1:])
else:
return a_string[0] + strip_upper(a_string[1:])
Write a recursive function all_upper
that takes a list of strings as a
parameter and returns a list of booleans, True for strings in the list that
are all uppercase, False otherwise. Recall that the string class has an
isupper
method that checks if it is all uppercase. Show answer.
def all_upper(strings):
if len(strings) == 0:
return []
else:
return [strings[0].isupper()] + all_upper(strings[1:])
1101 (Show answer)
13
111 (Show answer)
7
10010+01011 (Show answer)
1
10010 18
+01011 11
------ --
11101 29
What is the Big-O worst-case time complexity of the following Python code?
Assume that list11
and list2
are lists of the same length. Show answer.
def difference(list1, list2):
result = []
for val1 in list1:
if val1 not in list2:
result.append(val1)
return result
The outer loop has n iterations, while the in
operation has a
worst-case time complexity of n, so the total worst-case time complexity
is \(O(n^2)\).
The not in
operator in this context is a shortcut for not (val1 in list2)
. The worst case time complexity for in
on a list is
\(O(n)\) because we potentially have to examine all the
elements in the list. The average case is still \(O(n)\),
because on average we will need to examine half the elements.
How could solve this more efficiently? With the subtraction operator on sets.
There are several problems with this recursive implementation of
fibonacci
. What are they? Show
answer.
def fibonacci(n):
""" Return nth fibonacci number """
if n == 1 or 2:
return 1
else:
fibonacci(n[1:]) + fibonacci(n[2:])
n == 1 or 2
is the same as (n == 1) or 2
and is always True because 2
always evaluates to True. Should be n == 1 or n == 2
.n
is an integer, and so the slicing operator is not defined. The
recursive case should be n-1
and n-2
.return
in the recursive case.Translate the following function using NumPy to just use Python built-ins. Assume a_list
is a list
of floats and lower
is a single float: Show answer.
def sum_above(a_list, lower):
a_list = np.array(a_list)
return np.sum(a_list[a_list > lower])
def sum_above(a_list, lower):
""" Sum all value in a_list greater than lower """
result = 0
for val in a_list:
if val > lower:
result += val
return result
After the following code executes what are the values of b
and c
? Show answer.
a = { 1: "a", 3: "c", 26: "z", 10: "j" }
b = sorted(a.items())
c = sorted(list(a.items()) + [(3,"a")])
>>> b
[(1, 'a'), (3, 'c'), (10, 'j'), (26, 'z')]
>>> c
[(1, 'a'), (3, 'a'), (3, 'c'), (10, 'j'), (26, 'z')]
Recall that when tuples are compared, the first elements are compared, if equal, the second elements are compared and so on.
One useful property of averages is that we can compute the average without having the values in memory by maintaining a “running” sum and count of the number of values observed. Implement a class RunningAverage
that maintains a running average of values added with an add
method. That is it can perform the following computation. Show answer.
>>> mean = RunningAverage()
>>> for val in range(1, 5):
mean.add(val)
>>> mean.average()
2.5
>>> mean.add(5)
>>> mean.average()
3.0
class RunningAverage:
def __init__(self):
self.total = 0
self.count = 0
def add(self, value):
self.total += value
self.count += 1
def average(self):
return self.total / self.count