Unit 4 Review Questions
Quiz 4 is on Wed Nov 4 (MWF) / Thu Nov 5 (TuTh).
It covers Unit 4 (Weeks 9–10: while loops, sets, slicing, string methods, immutability), and it builds on everything since Week 2, especially the for loops, lists, dictionaries, and pytest from Unit 3.
File input and output will not be on the quiz.
How the quiz works.
Quiz 4 is on a computer, in Thonny, with no AI and no notes.
You will answer a few short questions about the problem first, then write one function in quiz4.py and one test function in test_quiz4.py, and submit both to Gradescope.
Section 7 is a practice quiz with the same mechanics, so the format will not surprise you.
Using AI with this review. These questions are for studying, and you may use AI to help. Try each question yourself first, then check your answer. If you ask AI to explain an answer, close the chat and redo the question on your own.
1. Choosing a loop
For each task, say whether a for loop or a while loop is the better tool, and give a one-sentence reason.
The question to ask yourself: do I know how many times the loop will run before it starts?
| # | Task | Loop | Reason |
|---|---|---|---|
| a | Add up every number in a list of prices. | for |
One pass per item; the list's length sets the count. |
| b | Keep asking the user for a PIN until they type exactly 4 digits. | while |
You can't know how many wrong tries the user will make. |
| c | Roll a die until you roll a 6, and count the rolls. | while |
The number of rolls depends on a random event. |
| d | Print the numbers 10, 8, 6, 4, 2. | for |
range(10, 0, -2) gives exactly these values. |
| e | Count the vowels in a word. | for |
One pass per character. |
| f | Double a savings balance each year until it passes $1,000,000. | while |
The number of years is what you're trying to find. |
g. A student writes this code for task (a).
It works. What makes it a worse choice than a for loop?
It needs three extra pieces (initialize i, check i, update i) that a for loop does for you. Forgetting i += 1 makes it run forever. for price in prices: can't make that mistake.
2. Tracing while loops
a. Label the three parts of this loop: initialization, condition, and update. Then say how many times the loop body runs and what is printed.
Initialization: count = 0 and total = 50. Condition: total > 10. Update: total = total // 2 (with count += 1 counting). The body runs 3 times (50 → 25 → 12 → 6). Prints 3 6.
b. How many times does this loop run, and what is printed?
5 times (1 → 3 → 9 → 27 → 81 → 243). Prints 5 243.
c. This loop never ends. Explain why, and change one character so it stops.
k goes 10, 7, 4, 1, −2, −5, … and skips over 0, so k != 0 is always true. Change != to > (making the condition k > 0), or change 3 to 2/5/1 so k lands on 0.
d. What is wrong with this input-validation loop? Rewrite it so it works.
The update is missing: age never changes inside the loop, so one bad value loops forever. Add age = int(input("Age: ")) as the last line of the loop body.
3. Sets
a. Give the value of each expression.
| Expression | Value |
|---|---|
len({3, 1, 3, 2}) |
3 |
len(set("banana")) |
3 |
"n" in set("banana") |
True |
sorted(set("banana")) |
["a", "b", "n"] |
{1, 2} == {2, 1} |
True |
set() vs. {} (what type is each?) |
set() is an empty set; {} is an empty dictionary. |
b. Trace the code and give the final value of dups and of len(seen).
nums = [4, 7, 4, 1, 7, 7]
seen = set()
dups = []
for n in nums:
if n in seen and n not in dups:
dups.append(n)
seen.add(n)
dups is [4, 7] and len(seen) is 3. The third 7 is already in dups, so it isn't added again.
c. Why can't you write seen[0] if seen is a set?
Sets have no order, so they have no indexes. You can only ask whether a value is in the set (or loop over it).
4. Slicing and string methods
a. Let s = "Shenandoah". Give the value of each expression.
| Expression | Value |
|---|---|
s[0:5] |
"Shena" |
s[5:] |
"ndoah" |
s[-3:] |
"oah" |
s[::2] |
"Seada" |
s[:3].upper() |
"SHE" |
s[::-1] |
"haodnanehS" |
s[:20] |
"Shenandoah" (a slice past the end is not an error) |
b. Let d = "2026-11-04". Give the value of each expression.
| Expression | Value |
|---|---|
d[:4] |
"2026" |
d[5:7] |
"11" |
d.split("-") |
["2026", "11", "04"] |
"/".join(d.split("-")) |
"2026/11/04" |
c. Let w = " Go Dukes! ". Give the value of each expression.
| Expression | Value |
|---|---|
w.strip().lower() |
"go dukes!" |
w.replace(" ", "") |
"GoDukes!" |
w.strip().upper().count("D") |
1 |
d. Slicing with [start:stop:step] works like range(start, stop, step).
Write a slice of s that gives the same characters as this loop builds.
s[1::3] (or s[1:len(s):3])
5. Immutability and aliasing
a. What does this print? Explain why name did not change.
dukes. Strings are immutable: upper() returns a new string, and this code throws that new string away. Use name = name.upper() to keep it.
b. What does this print?
def tidy(name):
name = name.strip().title()
return name
n = " ada lovelace "
m = tidy(n)
print(repr(n), repr(m))
' ada lovelace ' 'Ada Lovelace'. Inside the function, name is rebound to a new string; the caller's n is untouched.
c. What does this print?
['go', 'dukes', '!'] True. Lists are mutable, and words and w name the same list, so append changes the caller's list too.
d. In one or two sentences, explain why (b) and (c) behave differently.
(b) creates a new string and rebinds a local name, which can't affect the caller. (c) changes the one list object both names refer to. Sets and dictionaries behave like lists here.
6. Unit 3 warm-up
These skills will show up again on Quiz 4.
a. Write a for loop that builds a dictionary counting how many words in words have each length.
For ["go", "big", "jmu", "dukes"] the result is {2: 1, 3: 2, 5: 1}.
counts = {}
for word in words:
size = len(word)
if size in counts:
counts[size] += 1
else:
counts[size] = 1
b. A function is_palindrome(word: str) -> bool ignores case.
Write one pytest test function with three assert statements: an ordinary True case, an ordinary False case, and an edge case.
def test_is_palindrome():
assert is_palindrome("Racecar") is True
assert is_palindrome("dukes") is False
assert is_palindrome("") is True # or a one-letter word
7. Practice quiz (similar mechanics to Quiz 4)
Do this one in Thonny, with a 25-minute timer, the way you will on quiz day.
Download practice4.py and test_practice4.py from Canvas, answer the short questions on paper, then write the code.
The problem
Pick a positive whole number. Replace it with the sum of the squares of its digits, and repeat. For 13: 1² + 3² = 10, then 1² + 0² = 1, then 1² = 1 again. Some numbers reach 1; others fall into a cycle that repeats forever. Either way, the chain stops the moment you reach a number you have already seen.
Write digit_chain(n: int) -> list[int], which returns every number in the chain, in order, stopping before the first repeat.
| Call | Returns |
|---|---|
digit_chain(13) |
[13, 10, 1] |
digit_chain(1) |
[1] |
digit_chain(4) |
[4, 16, 37, 58, 89, 145, 42, 20] (the next number would be 4 again) |
Short questions (answer before you code)
- Write the chain for
19by hand.[19, 82, 68, 100, 1] - Your function needs two loops. Which one is a
forloop and which is awhileloop? Why? The loop over the digits of one number is aforloop (you know exactly how many digits there are). The loop that builds the chain is awhileloop (you don't know how long the chain will be). - What data type will you use to remember the numbers you've already seen, and why?
A set, because the question each time is "have I seen this?" and
inon a set answers it directly. (A list also works but is the slower, less fitting tool.) - Name the initialization, condition, and update of your
whileloop. Init: an empty set and list, and the startingn. Condition:n not in seen. Update: replacenwith the sum of the squares of its digits.
Starter code
practice4.py:
def digit_chain(n: int) -> list[int]:
"""Return the chain of digit-square sums starting at n.
Args:
n: a positive whole number
Returns:
Each number in the chain, in order, stopping before the first repeat.
"""
pass # replace with your code
if __name__ == "__main__":
print(digit_chain(13)) # [13, 10, 1]
print(digit_chain(1)) # [1]
print(digit_chain(4)) # [4, 16, 37, 58, 89, 145, 42, 20]
test_practice4.py: