Unit 3 Review Questions
Quiz 3 is done on paper, so its focus is reading and writing code by hand: you will trace loops to predict exactly what they do, and write short loops, list and dictionary statements, and tests without running anything. These review questions follow that lead. Work each one with a pencil first, then check your answer in Thonny. Questions are grouped by learning objective (Weeks 6 and 8), with a few questions per objective.
Week 6: for loops over lists and ranges
1. What is printed, and how many times does the loop body run?
5 5 3 8 1 9
The loop body runs 3 times: range(5, 0, -2) produces 5, 3, and 1.
2. How many times does the body of each loop run?
for i in range(4):4 (0, 1, 2, 3)for i in range(2, 9):7 (2 through 8)for i in range(0, 20, 5):4 (0, 5, 10, 15)for i in range(5, 5):0 (the loop never runs)
3. Given a list named scores, write a for loop that prints each score with its position, counting positions from 1. For scores = [90, 85, 72], it should print:
for i in range(len(scores)):
print(f"{i + 1}: {scores[i]}")
4. For the code below, determine (1) how many times the loop runs, and (2) exactly what is printed on the screen. Notice that end=" " keeps each value on the same line, followed by a space.
movies = ["Alien", "Brazil", "Heat", "Her", "Up", "Jaws", "Ran"]
for i in range(0, len(movies), 3):
print(movies[i], end=" ")
print(i)
The loop runs 3 times (i is 0, 3, and 6).
Alien Her Ran 6
After the loop ends, i still holds its last value, 6, which is printed on the same line.
Week 6: accumulating and filtering with a loop
1. Write a function named long_names that takes a list of strings, names, and an integer, min_len, and returns a new list containing only the names that have at least min_len characters, in their original order. The original list must not change.
For example, long_names(["Al", "Barb", "Cy", "Dolores"], 4) returns ["Barb", "Dolores"].
def long_names(names, min_len):
result = []
for name in names:
if len(name) >= min_len:
result.append(name)
return result
2. Write a function named spread that takes a non-empty list of numbers and returns the largest value minus the smallest value. Use a loop to find both values; do not use min() or max().
For example, spread([7, 3, 9, 4]) returns 6, and spread([5]) returns 0.
def spread(numbers):
low = numbers[0]
high = numbers[0]
for n in numbers:
if n < low:
low = n
if n > high:
high = n
return high - low
3. Write a function named count_matches that takes a list of strings, items, and a string, target, and returns the number of times target appears in the list. You must use a for loop; use of the count() method is not allowed.
For example, count_matches(["a", "b", "a"], "a") returns 2, and count_matches([], "a") returns 0.
def count_matches(items, target):
count = 0
for item in items:
if item == target:
count += 1
return count
4. Write a function named get_passing that takes an integer, count, and prompts the user for that many scores (each an int). It returns a list of only the scores that are 60 or higher, in the order they were entered.
In the following example, get_passing(4) is called and the user enters 75, 40, 60, and 59. The function returns [75, 60].
def get_passing(count):
passing = []
for i in range(count):
score = int(input("Score: "))
if score >= 60:
passing.append(score)
return passing
Week 6: changing a list in place
1. What is printed by each print statement?
items = [30, 10, 20]
items.append(5)
items[1] = 50
items.remove(30)
items.sort()
print(items)
scores = [3, 1, 2]
scores = scores.sort()
print(scores)
[5, 20, 50] None
The list goes [30, 10, 20] → [30, 10, 20, 5] → [30, 50, 20, 5] → [50, 20, 5] → [5, 20, 50]. The sort() method changes the list in place and returns None, so assigning its result back to scores replaces the list with None.
2. Given a list named roster that currently holds ["Bo", "Cy", "Di"], write one statement for each task, in order: (a) add "Zed" to the end, (b) replace the first name with "Amy", (c) remove "Cy", and (d) sort the list alphabetically.
roster.append("Zed")
roster[0] = "Amy"
roster.remove("Cy")
roster.sort()
3. Write a function named double_all that takes a list of numbers and doubles every element in place. It should change the list it was given and return nothing.
For example, after data = [1, 2, 3] and double_all(data), the list data is [2, 4, 6].
def double_all(numbers):
for i in range(len(numbers)):
numbers[i] = numbers[i] * 2
The loop must go by index. Writing for n in numbers: n = n * 2 would only change the loop variable, not the list.
Week 8: looping over the characters of a string
1. Write a function named count_marks that takes a string, text, and returns how many of its characters are an exclamation point, question mark, or period.
For example, count_marks("Hi! Why? Ok.") returns 3.
def count_marks(text):
count = 0
for ch in text:
if ch in "!?.":
count += 1
return count
2. Write a function named mask_digits that takes a string, text, and returns a new string in which every digit character ("0" through "9") is replaced by "#" and every other character is unchanged.
For example, mask_digits("Call 555-0199") returns "Call ###-####".
def mask_digits(text):
result = ""
for ch in text:
if ch in "0123456789":
result += "#"
else:
result += ch
return result
3. What does each call return?
def first_double(text):
for i in range(len(text) - 1):
if text[i] == text[i + 1]:
return i
return -1
first_double("balloon")2 (the twolcharacters start at index 2)first_double("abc")-1first_double("a")-1 (range(0)never runs)first_double("")-1 (range(-1)never runs)
4. Write a function named repeat_each that takes a string, text, and returns a new string in which every character appears twice in a row.
For example, repeat_each("Hi!") returns "HHii!!", and repeat_each("") returns "".
def repeat_each(text):
result = ""
for ch in text:
result += ch * 2
return result
Week 8: counting with a dictionary
1. Write a function named tally_votes that takes a list of strings, votes, and returns a dictionary that maps each name to the number of times it appears in the list.
For example, tally_votes(["red", "blue", "red"]) returns {"red": 2, "blue": 1}.
def tally_votes(votes):
counts = {}
for vote in votes:
if vote in counts:
counts[vote] += 1
else:
counts[vote] = 1
return counts
2. Trace the code. What is printed by the two print statements? Then say what would happen if the next line were print(stock["paper"]).
stock = {"pens": 10, "tape": 4}
stock["tape"] += 3
stock["glue"] = 6
if "ink" in stock:
stock["ink"] += 1
else:
stock["ink"] = 1
print(stock)
print(len(stock))
{'pens': 10, 'tape': 7, 'glue': 6, 'ink': 1}
4
print(stock["paper"]) raises a KeyError, because "paper" is not a key. That is why the code above checks in before adding to a count.
3. Write a function named apply_sales that takes a dictionary, stock, mapping item names to how many are in stock, and a list of strings, sales, with one entry for each item sold. For every sale of an item that is in stock, subtract 1 from its count. Sales of items that are not in stock are ignored. Return the updated dictionary.
For example, apply_sales({"pens": 3, "tape": 1}, ["pens", "tape", "pens", "glue"]) returns {"pens": 1, "tape": 0}.
def apply_sales(stock, sales):
for item in sales:
if item in stock:
stock[item] -= 1
return stock
Without the in check, the sale of "glue" would raise a KeyError.
4. Evaluate each expression below. If an error occurs, write the word Error. Use double quotes for string values.
planets = ["Mars", "Venus", "Earth", "Saturn", "Mars"]
moons = {"Earth": 1, "Mars": 2, "Jupiter": 95}
| Expression | Result |
|---|---|
moons["Mars"] |
2 |
len(moons) |
3 |
planets[3][0] |
"S" |
planets[moons["Earth"]] |
"Venus" |
"Venus" in moons |
False |
moons[1] |
Error (KeyError: 1 is not a key) |
planets[moons["Mars"]][-1] |
"h" |
planets[5] |
Error (IndexError: valid indexes are 0 to 4) |
5. Given the following variables:
Write one line of code for each description.
- (a) Print the value of a boolean expression that tests whether the value of
itemis a key in the dictionarymenu. - (b) Insert the key in the variable
item, with the value in the variableprice, intomenu. - (c) Increase the price of
"soup"inmenuby 1. - (d) Print how many items are on the menu.
print(item in menu) menu[item] = price menu["soup"] += 1 print(len(menu))
Week 8: writing pytest tests
1. Here is a function in a file named stats.py:
def average(numbers):
"""Return the average of a list of numbers, or 0.0 if it is empty."""
if len(numbers) == 0:
return 0.0
total = 0
for n in numbers:
total += n
return total / len(numbers)
In a separate test file, write the import line and two pytest test functions: one for an ordinary input and one for an edge case.
from stats import average
def test_average_ordinary():
assert average([2, 4, 6]) == 4.0
def test_average_empty():
assert average([]) == 0.0
Other good edge cases: a one-element list such as average([5]) == 5.0.
2. A student wrote this version of count_marks and these three tests. Which tests fail, and what mistake in the function do they reveal?
def count_marks(text):
count = 0
for ch in text:
if ch in "!?":
count += 1
return count
def test_exclaim():
assert count_marks("Hi!") == 1
def test_period():
assert count_marks("Ok.") == 1
def test_empty():
assert count_marks("") == 0
Only test_period fails: count_marks("Ok.") returns 0, not 1. The function forgot to count periods because "." is missing from the string "!?". The other two tests pass, including the empty-string edge case.
3. Using the first_double function from the string-looping questions above (it lives in a file named text_tools.py), write the import line and three pytest tests: one where the repeated character is in the middle of the string, one where there is no repeated character, and one edge case with an empty string.
from text_tools import first_double
def test_double_in_middle():
assert first_double("balloon") == 2
def test_no_double():
assert first_double("abc") == -1
def test_empty_string():
assert first_double("") == -1
Other good edge cases: a double at the very start ("aab" returns 0) or at the very end ("abcc" returns 2).