Skip to content

Exercises - functools

Exercise 1🟢 Beginner Using partial, create two functions from a base multiply function: one that doubles a number and one that triples it.

def multiply(x, factor):
return x * factor
# expected:
# double(5) → 10
# triple(5) → 15

Exercise 2🟢 Beginner Using partial, create a greet_formally and greet_casually function from a base greet function.

def greet(style, name):
if style == "formal":
return f"Good day, {name}."
return f"Hey, {name}!"
# expected:
# greet_formally("Alice") → "Good day, Alice."
# greet_casually("Alice") → "Hey, Alice!"

Exercise 3🟡 Intermediate Using partial and map, convert a list of temperatures from Celsius to Fahrenheit without using a lambda.

def convert(unit, value):
if unit == "fahrenheit":
return (value * 9 / 5) + 32
celsius = [0, 10, 20, 30, 40]
# expected output: [32.0, 50.0, 68.0, 86.0, 104.0]

Exercise 4🟢 Beginner Add memoization to this slow function using lru_cache and verify the cache is being used.

import time
def slow_square(n):
time.sleep(1) # simulates expensive computation
return n ** 2
# expected:
# first call: slow_square(5) → takes ~1 second
# second call: slow_square(5) → instant (cached)
# slow_square.cache_info() → hits=1, misses=1

Exercise 5🟡 Intermediate Use lru_cache to memoize a recursive function that computes the nth triangular number (sum of all integers from 1 to n).

# triangular(1) = 1
# triangular(2) = 3 (1 + 2)
# triangular(3) = 6 (1 + 2 + 3)
# triangular(4) = 10 (1 + 2 + 3 + 4)
# expected:
# triangular(10) → 55
# triangular(100) → 5050

Exercise 6🟡 Intermediate Implement a memoized function that counts how many ways you can climb a staircase of n steps, taking either 1 or 2 steps at a time.

# climb(1) → 1 (only one way: [1])
# climb(2) → 2 ([1,1] or [2])
# climb(3) → 3 ([1,1,1], [1,2], [2,1])
# climb(4) → 5
# expected:
# climb(10) → 89
# climb(30) → 1346269

Exercise 7🟢 Beginner Create a Temperature class using total_ordering that compares temperatures by their value. Define only __eq__ and __lt__, and verify the auto-generated methods work.

t1 = Temperature(100)
t2 = Temperature(200)
# expected:
# t1 < t2 → True
# t1 > t2 → False ← auto-generated
# t1 <= t2 → True ← auto-generated
# t1 >= t2 → False ← auto-generated

Exercise 8🟡 Intermediate Create a Card class representing a playing card using total_ordering. Cards should be ordered by rank (2 lowest, Ace highest). Suit does not affect ordering.

ranks = ['2','3','4','5','6','7','8','9','10','J','Q','K','A']
c1 = Card('2', 'Hearts')
c2 = Card('A', 'Spades')
c3 = Card('K', 'Clubs')
# expected:
# c1 < c2 → True
# c2 > c3 → True
# sorted([c2, c1, c3]) → [Card(2), Card(K), Card(A)]

Exercise 9🔴 Advanced Create a SemanticVersion class using total_ordering that parses version strings and compares them correctly.

v1 = SemanticVersion("1.2.0")
v2 = SemanticVersion("1.10.0")
v3 = SemanticVersion("2.0.0")
# expected:
# v1 < v2 → True (1.2 < 1.10 — numeric, not lexicographic!)
# v2 < v3 → True
# sorted(["2.0.0", "1.10.0", "1.2.0"]) → ["1.2.0", "1.10.0", "2.0.0"]

Exercise 10🟢 Beginner Use singledispatch to create a describe function that returns a different description depending on the type of the argument.

# expected:
# describe(42) → "A number: 42"
# describe("hello") → "A string of 5 characters"
# describe([1, 2, 3]) → "A list of 3 items"
# describe(3.14) → TypeError: Unsupported type

Exercise 11🟡 Intermediate Use singledispatch to create a serialize function that converts different types to a JSON-friendly string representation.

from datetime import date
# expected:
# serialize(42) → "42"
# serialize(3.14) → "3.14"
# serialize("hello") → '"hello"'
# serialize([1, 2, 3]) → "[1, 2, 3]"
# serialize(date(2024, 1, 1)) → "2024-01-01"

Exercise 12🔴 Advanced Use singledispatch to create a flatten function that recursively flattens nested structures of mixed types into a single list.

# expected:
# flatten(1) → [1]
# flatten("hi") → ["hi"]
# flatten([1, [2, 3]]) → [1, 2, 3]
# flatten([1, [2, [3, [4]]]]) → [1, 2, 3, 4]
# flatten((1, (2, 3))) → [1, 2, 3] ← tuples too

Try solving each exercise using only the relevant functools tool — resist reaching for a lambda or a loop where partial, cache, or singledispatch would express the intent more clearly.