Exercises: Functions — Writing Reusable Code
These exercises progress from basic function definitions to design challenges that require you to think about decomposition and reuse. Every exercise has a coding component — write and run each solution.
Difficulty Guide: - ⭐ Foundational (5-10 min each) - ⭐⭐ Intermediate (10-20 min each) - ⭐⭐⭐ Challenging (20-40 min each) - ⭐⭐⭐⭐ Advanced/Research (40+ min each)
Part A: Basic Function Definitions ⭐
A.1. Write a function say_hello() that takes no parameters and prints "Hello, World!". Call it three times.
A.2. Write a function greet(name) that takes a name as a parameter and prints a personalized greeting. Call it with at least three different names.
A.3. Write a function square(n) that returns the square of n. Store the result in a variable and print it. Then use the function directly inside a print statement without storing the result first.
A.4. Write a function is_even(n) that returns True if n is even, False otherwise. Test it with the numbers 0, 1, 7, 12, and -3.
A.5. Write a function print_separator(char, length) that prints a line of the given character repeated length times. For example, print_separator("-", 30) should print ------------------------------.
A.6. Write a function celsius_to_fahrenheit(celsius) that returns the Fahrenheit equivalent. The formula is: F = C * 9/5 + 32. Test it with 0, 100, and 37.
Part B: Parameters, Arguments, and Return Values ⭐⭐
B.1. Write a function create_greeting(name, greeting="Hello", punctuation="!") with default parameters. Demonstrate calling it:
- With just a name
- With a name and custom greeting
- With all three arguments
- Using keyword arguments in a different order
B.2. Write a function calculate_tip(bill_amount, tip_percent=18) that returns the tip amount and the total bill. Call it and unpack both return values.
B.3. Write a function find_min_max(numbers) that takes a list of numbers and returns both the minimum and maximum as a tuple. Do NOT use the built-in min() or max() — write the logic yourself with a loop.
B.4. Write a function count_vowels(text) that returns the number of vowels (a, e, i, o, u — both upper and lower case) in the given text. Test it with "Hello World" (expected: 3) and "AEIOU aeiou" (expected: 10).
B.5. Write a function repeat_string(text, n=2) that returns the text repeated n times with a space between each repetition. For example, repeat_string("ha", 3) should return "ha ha ha".
B.6. Write a function calculate_average(*scores) using *args that returns the average of any number of numeric arguments. Handle the case of zero arguments by returning 0.0. Test with calculate_average(85, 90, 78), calculate_average(100), and calculate_average(). Then try calculate_average([85, 90, 78]) and explain the error you get.
B.7. Write a function clamp(value, minimum, maximum) that returns value if it's between minimum and maximum, returns minimum if value is too low, and maximum if value is too high. For example:
- clamp(5, 0, 10) returns 5
- clamp(-3, 0, 10) returns 0
- clamp(15, 0, 10) returns 10
Part C: Scope and Variable Lifetime ⭐⭐
C.1. Predict the output of this code WITHOUT running it. Then run it to check your prediction:
x = 10
def change_x():
x = 20
print(f"Inside function: x = {x}")
change_x()
print(f"Outside function: x = {x}")
C.2. Predict whether this code will work or raise an error. Explain why BEFORE running it:
def add_one():
count = count + 1
return count
count = 5
result = add_one()
C.3. The following code works, but only because it leans on the global keyword:
count = 0
def increment():
global count
count += 1
increment()
increment()
print(count) # 2
Rewrite it without global: increment should take count as a parameter and return the new value, and the caller should store what it returns. The program should still print 2.
C.4. Predict the output of this code:
def outer():
x = "outer"
def inner():
print(x)
inner()
x = "global"
outer()
print(x)
Part D: Function Composition and Design ⭐⭐⭐
D.1. Build a small grade reporting system with three separate functions:
- calculate_weighted_average(scores, weights) — returns the weighted average
- letter_grade(average) — returns the letter grade (A/B/C/D/F)
- print_report(name, scores, weights) — calls the other two and prints a formatted report
Test it with: name="Alice", scores=[85, 92, 78], weights=[0.3, 0.4, 0.3].
D.2. Write a temperature conversion toolkit with these functions:
- celsius_to_fahrenheit(c) — returns F
- fahrenheit_to_celsius(f) — returns C
- celsius_to_kelvin(c) — returns K
- fahrenheit_to_kelvin(f) — calls fahrenheit_to_celsius then celsius_to_kelvin (function composition!)
Test each function and verify the results.
D.3. Write a validate_password(password) function that checks a password against these rules:
- At least 8 characters long
- Contains at least one uppercase letter
- Contains at least one lowercase letter
- Contains at least one digit
The function should return a tuple: (is_valid, list_of_problems). For example, validate_password("abc") might return (False, ["Too short", "No uppercase letter", "No digit"]).
D.4. Elena needs a function to process her nonprofit's survey data. Write three functions:
- clean_responses(responses) — takes a list of strings, strips whitespace, removes empty responses, and converts to lowercase. Returns the cleaned list.
- count_responses(responses) — takes a list of strings and returns a dictionary counting how many times each response appears. (Dictionaries arrive in Chapter 9. If you haven't met them yet, return a tuple (yes_count, no_count, other_count) instead.)
- survey_report(responses) — uses the two functions above to print a clean summary.
Test with: ["Yes", " no ", "YES", "", " Maybe ", "yes", "No", " "]
D.5. Write a mini calculator with these functions:
- add(a, b), subtract(a, b), multiply(a, b), divide(a, b)
- calculate(a, op, b) — takes two numbers and an operator string ("+", "-", "*", "/") and calls the appropriate function. Returns the result, or None if the operator is invalid.
Test: calculate(10, "+", 5) should return 15.
Part E: Refactoring Challenges ⭐⭐⭐
E.1. The following code works but is poorly organized. Refactor it into at least 4 well-named functions:
# Shopping cart — all in one block
items = []
prices = []
while True:
print("\n1. Add item 2. View cart 3. Total 4. Quit")
choice = input("Choice: ")
if choice == "1":
name = input("Item name: ")
price = float(input("Price: "))
items.append(name)
prices.append(price)
print(f"Added {name} (${price:.2f})")
elif choice == "2":
if items:
for i in range(len(items)):
print(f" {i+1}. {items[i]} - ${prices[i]:.2f}")
else:
print("Cart is empty.")
elif choice == "3":
total = 0
for p in prices:
total += p
tax = total * 0.08
print(f"Subtotal: ${total:.2f}")
print(f"Tax: ${tax:.2f}")
print(f"Total: ${total + tax:.2f}")
elif choice == "4":
print("Goodbye!")
break
E.2. Write a text statistics analyzer. Given a string of text, create and use these functions:
- word_count(text) — returns the number of words (hint: text.split() breaks a string into a list of words; Chapter 7 covers it fully)
- sentence_count(text) — returns the number of sentences (count periods, exclamation marks, and question marks)
- average_word_length(text) — returns the average length of words
- longest_word(text) — returns the longest word
- text_summary(text) — calls all the above and prints a formatted summary
Test with a paragraph of at least 3 sentences.
Part F: Debugging Exercises ⭐⭐
F.1. Find and fix the bug in each of these functions:
# Bug 1
def calculate_average(scores):
total = 0
for score in scores:
total += score
average = total / len(scores)
result = calculate_average([85, 90, 78])
print(f"Average: {result}") # Prints: Average: None
# Bug 2
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
message = greet(greeting="Hi", "Alice")
print(message)
# Bug 3
def build_list(item, items=[]):
items.append(item)
return items
list1 = build_list("apple")
list2 = build_list("banana")
print(list1) # Expected: ['apple'], Got: ['apple', 'banana']
print(list2) # Expected: ['banana'], Got: ['apple', 'banana']
# Bug 4
def is_positive(n):
if n > 0:
return True
result = is_positive(-5)
if result == False:
print("Negative!")
else:
print("Positive!") # Prints "Positive!" even though n is -5
F.2. This function has a logic error. The function should return the sum of all positive numbers in the list, but it doesn't work correctly. Find and fix the bug:
def sum_positives(numbers):
total = 0
for num in numbers:
if num > 0:
total += num
return total
return total
print(sum_positives([1, -2, 3, -4, 5])) # Expected: 9, Got: 1
Part M: Mixed Practice (Interleaved) ⭐⭐
These problems mix function concepts with material from previous chapters.
M.1. (Ch 3 + Ch 6) Write a function safe_divide(a, b) that takes two arguments, converts them to floats (type casting from Ch 3), and returns the result of dividing a by b. If b is zero, return None and print a warning. Test with safe_divide("10", "3") and safe_divide(7, 0).
M.2. (Ch 4 + Ch 6) Write a function classify_number(n) that returns a string describing the number: "positive even", "positive odd", "negative even", "negative odd", or "zero". Use the conditional logic from Chapter 4 inside a function.
M.3. (Ch 5 + Ch 6) Write a function fizzbuzz(n) that returns a list of strings from 1 to n following the FizzBuzz rules: multiples of 3 become "Fizz", multiples of 5 become "Buzz", multiples of both become "FizzBuzz", and all others become the number as a string. Use a loop (Ch 5) inside a function (Ch 6).
M.4. (Ch 5 + Ch 6) Rewrite the "sum until done" loop from the Spaced Review section as a function called sum_until_done() that returns the total. The function should handle the entire input loop internally and return the final sum.
Part G: Extension Challenges ⭐⭐⭐⭐
G.1. Write a compose(f, g) function that takes two functions as arguments and returns a new function that applies g first, then f. Demonstrate it:
def double(x):
return x * 2
def add_one(x):
return x + 1
double_then_add = compose(add_one, double)
print(double_then_add(5)) # Should print 11 (double 5 = 10, add 1 = 11)
Hint: functions are objects in Python — you can pass them as arguments and return them from other functions.
G.2. Build a simple function-based encryption/decryption system:
- encrypt(message, shift) — apply a Caesar cipher with the given shift to each letter. Non-letters stay unchanged.
- decrypt(message, shift) — reverse the encryption.
- brute_force_decrypt(message) — try all 26 shifts and print each result.
Test by encrypting "Hello World" with shift 3 (you should get "Khoor Zruog"), then decrypting it.
Hint: ord("A") gives a character's code point (65) and chr(65) turns it back into "A". Shift the code point, and use % to wrap around from Z back to A.
Solutions
Selected solutions are in Appendix: Answers to Selected Exercises.