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.