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 (85, 90, 78), (100,), and no arguments.

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. Fix the following code so that the function properly increments and returns the new count — without using the global keyword:

count = 0

def increment():
    global count
    count += 1

increment()
increment()
print(count)  # 2

Rewrite it so increment takes count as a parameter and returns the new value.

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. - 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 - 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, then decrypting it.


Solutions

Selected solutions in appendices/answers-to-selected.md.