Skip to content

Exercises - function composition

Exercise 1 β€” 🟒 Beginner Implement a compose function that combines two functions right to left, then use it to build a function that negates a doubled number.

# expected:
# double = lambda x: x * 2
# negate = lambda x: -x
# negate_double = compose(negate, double)
# negate_double(5) β†’ -10 β†’ double(5)=10, negate(10)=-10
# negate_double(3) β†’ -6

Exercise 2 β€” 🟒 Beginner Using compose, build a string transformation that strips whitespace and then capitalises the first letter.

# expected:
# clean = compose(str.capitalize, str.strip)
# clean(" hello world ") β†’ "Hello world"
# clean(" python ") β†’ "Python"

Exercise 3 β€” 🟑 Intermediate Implement compose_all using reduce that composes any number of functions right to left, then verify the order of application.

from functools import reduce
# expected:
# add_one = lambda x: x + 1
# double = lambda x: x * 2
# square = lambda x: x ** 2
# pipeline = compose_all(add_one, double, square)
# pipeline(3) β†’ 19 β†’ square(3)=9, double(9)=18, add_one(18)=19
# pipeline(2) β†’ 9 β†’ square(2)=4, double(4)=8, add_one(8)=9

Exercise 4 β€” 🟒 Beginner Implement pipe_all and use it to build a number processing pipeline that squares a number, then doubles it, then subtracts one.

# expected:
# pipeline = pipe_all(
# lambda x: x ** 2, # 1. square
# lambda x: x * 2, # 2. double
# lambda x: x - 1, # 3. subtract one
# )
# pipeline(3) β†’ 17 β†’ square(3)=9, double(9)=18, subtract(18)=17
# pipeline(4) β†’ 31 β†’ square(4)=16, double(16)=32, subtract(32)=31

Exercise 5 β€” 🟑 Intermediate Use pipe_all to build a name formatting pipeline that cleans and formats a name consistently.

# expected:
# format_name = pipe_all(
# str.strip,
# str.lower,
# str.title,
# )
# format_name(" ALICE SMITH ") β†’ "Alice Smith"
# format_name(" bob JONES ") β†’ "Bob Jones"
# format_name("CHARLIE ") β†’ "Charlie"

Exercise 6 β€” 🟑 Intermediate Use pipe_all to build a number validation and transformation pipeline that filters a list of raw string inputs into clean positive integers.

raw = [" 42 ", " -3 ", " 0 ", " 17 ", " -99 ", " 5 "]
# expected pipeline steps:
# 1. strip whitespace
# 2. convert to int
# 3. keep only positive numbers
# expected output: [42, 17, 5]

Exercise 7 β€” 🟑 Intermediate Build a text sanitization pipeline that prepares user input for storage in a database.

import re
raw_inputs = [
" Alice Smith!! ",
" BOB JONES... ",
" charlie, brown ",
]
# expected pipeline steps:
# 1. strip whitespace
# 2. lowercase
# 3. remove punctuation
# 4. normalize multiple spaces into one
# 5. title case
# expected output:
# ["Alice Smith", "Bob Jones", "Charlie Brown"]

Exercise 8 β€” 🟑 Intermediate Build a pipeline that processes a list of raw email addresses and returns only valid, normalized ones.

import re
emails = [
" Alice@Example.COM ",
" invalid-email ",
" BOB@GMAIL.COM ",
" not_an_email ",
" Charlie@Domain.org ",
]
# expected pipeline steps:
# 1. strip whitespace
# 2. lowercase
# 3. keep only strings containing "@"
# expected output:
# ["alice@example.com", "bob@gmail.com", "charlie@domain.org"]

Exercise 9 β€” πŸ”΄ Advanced Build a log processing pipeline that parses raw log lines and extracts structured data.

raw_logs = [
" [INFO] Server started on port 8080 ",
" [ERROR] Connection refused: timeout ",
" [WARN] Memory usage above 80% ",
" [INFO] Request received: GET /api ",
" [ERROR] Database connection failed ",
]
# expected pipeline steps:
# 1. strip whitespace
# 2. parse into {"level": ..., "message": ...}
# 3. keep only ERROR level logs
# 4. extract just the message
# expected output:
# ["Connection refused: timeout", "Database connection failed"]

Exercise 10 β€” 🟑 Intermediate Implement both compose and pipe_all, then show they produce the same result when functions are listed in reverse order.

add_one = lambda x: x + 1
double = lambda x: x * 2
square = lambda x: x ** 2
# expected β€” these should produce identical results:
# compose_all(add_one, double, square)(3) == pipe_all(square, double, add_one)(3)
# both β†’ 19

Exercise 11 β€” πŸ”΄ Advanced Build a reusable data processing framework using pipe_all that applies a pipeline to every item in a list, filters results, and reduces to a final value.

from functools import reduce
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# expected pipeline:
# 1. square each number
# 2. keep only numbers greater than 20
# 3. sum them all with reduce
# expected output: 364 (25 + 36 + 49 + 64 + 81 + 100 + ... wait, recalculate)
# 25 + 36 + 49 + 64 + 81 + 100 = 355

Exercise 12 β€” πŸ”΄ Advanced Implement a memoized_pipe β€” a pipeline where each step’s results are cached so repeated calls with the same input skip recomputation.

from functools import lru_cache
# expected:
# pipeline = memoized_pipe(
# lambda x: x ** 2, # step 1 β€” cached
# lambda x: x * 2, # step 2 β€” cached
# lambda x: x - 1, # step 3 β€” cached
# )
# pipeline(5) β†’ 49 (computed)
# pipeline(5) β†’ 49 (from cache β€” no recomputation)
# pipeline(3) β†’ 17 (computed)

Try building each pipeline from the smallest possible functions β€” the goal is to write steps so focused that each one does exactly one thing and could be tested in isolation.