The Rules
3 min- Solve each problem using only comprehensions, lambdas, generators,
map/filter,itertools, or built-ins likesum/max/any/all. - No explicit
for x in seq:loops with bodies that mutate state. Generators with internal loops are fine. - Aim for one expression per solution where reasonable.
Functional Python isn't about banning loops forever. It's about knowing how much you can do without them. Drilling the muscle here makes day-to-day Python more compact.
Problem 1 · Sum of Squares of Evens
5 minGiven 1..20, return the sum of squares of just the even numbers.
assert problem1() == 4 + 16 + 36 + 64 + 100 + 144 + 196 + 256 + 324 + 400
Hint
def problem1(): return sum(n * n for n in range(1, 21) if n % 2 == 0)
One generator expression. Filter the evens, square, sum.
Problem 2 · The Anagram Index
6 minGiven a list of words, return a dict mapping each "sorted-letters key" to a list of words that share it. Anagrams cluster together.
words = ["listen", "silent", "evil", "vile", "live", "python", "rust"] # expected: {'eilnst': ['listen', 'silent'], 'eilv': ['evil', 'vile', 'live'], # 'hnopty': ['python'], 'rstu': ['rust']}
Hint
def problem2(words): keys = {"".join(sorted(w)) for w in words} return {k: [w for w in words if "".join(sorted(w)) == k] for k in keys}
A set comp for the unique keys, then a dict comp filtering by each. Two comprehensions, one nested.
Problem 3 · First N Triangular Numbers
5 minWrite a generator triangular() that yields 1, 3, 6, 10, 15, ... forever. Return the first 10 as a list using islice.
Hint
from itertools import islice def triangular(): n, total = 1, 0 while True: total += n yield total n += 1 def problem3(): return list(islice(triangular(), 10))
An infinite generator + islice. Memory-safe even if you asked for the first million.
Problem 4 · Are All Adults?
5 minGiven a list of dicts with an age field, return True iff every age is 18 or above. Use all.
people = [{"name": "A", "age": 22}, {"name": "B", "age": 17}, {"name": "C", "age": 30}] # → False (B is 17)
Hint
def problem4(people): return all(p["age"] >= 18 for p in people)
One generator expression fed to all. Returns True only if every item is truthy. The dual is any for "at least one".
Problem 5 · Running Max
7 minWrite a generator running_max(seq) that yields the running maximum.
list(running_max([3, 1, 4, 1, 5, 9, 2, 6])) # → [3, 3, 4, 4, 5, 9, 9, 9]
Hint
def running_max(seq): current = None for n in seq: current = n if current is None else max(current, n) yield current
State that persists between yields (current) — a generator's killer feature.
Problem 6 · Top-3 by Frequency
8 minGiven a string of words separated by spaces, return the 3 most-frequent words. Use Counter (it's technically a dict but considered functional).
text = "the cat sat on the mat with the dog and the cat" # → [('the', 4), ('cat', 2), ('sat', 1)] # or whatever order ties break in
Hint
from collections import Counter def problem6(text): return Counter(text.split()).most_common(3)
One line. Counter's most_common(n) gives the top-N as a list of (value, count) tuples.
Putting It All Together · olympics.py
8 minSave all six solutions in one file, each as a named function. Add a test runner at the bottom that asserts each problem's expected output.
Show one complete solution
# olympics.py — functional-only solutions from itertools import islice from collections import Counter def problem1(): return sum(n * n for n in range(1, 21) if n % 2 == 0) def problem2(words): keys = {"".join(sorted(w)) for w in words} return {k: [w for w in words if "".join(sorted(w)) == k] for k in keys} def triangular(): n, total = 1, 0 while True: total += n yield total n += 1 def problem3(): return list(islice(triangular(), 10)) def problem4(people): return all(p["age"] >= 18 for p in people) def running_max(seq): current = None for n in seq: current = n if current is None else max(current, n) yield current def problem5(seq): return list(running_max(seq)) def problem6(text): return Counter(text.split()).most_common(3) # --- tests --- if __name__ == "__main__": assert problem1() == 2870 p2 = problem2(["listen", "silent", "evil", "vile", "live", "python", "rust"]) assert sorted(p2["eilnst"]) == ["listen", "silent"] assert sorted(p2["eilv"]) == ["evil", "live", "vile"] assert problem3() == [1, 3, 6, 10, 15, 21, 28, 36, 45, 55] assert problem4([{"age": 22}, {"age": 17}]) is False assert problem4([{"age": 22}, {"age": 30}]) is True assert problem5([3, 1, 4, 1, 5, 9, 2, 6]) == [3, 3, 4, 4, 5, 9, 9, 9] assert problem6("the cat sat on the mat with the dog and the cat")[0] == ("the", 4) print("All 6 problems pass.")
Non-negotiables: every problem solved without a for-loop in the consumer (generators may contain them internally). Each test asserts an exact answer. if __name__ == "__main__": gates the tests so importing the file doesn't auto-run them.
Recap
3 minSix problems, six functional shapes. Sum-with-filter. Nested comprehension for grouping. Infinite generator + islice. all/any over a generator. Stateful generator with running aggregate. Counter.most_common for top-N. The functional toolkit covers more than you'd guess — and produces code that's short, fast, and obvious in intent. Use these shapes when they fit; fall back to a loop only when they get awkward.
The next 6 lessons (31-36) shift to recursion. We'll meet base cases, classic recursive functions, and recursive turtle art — trees, Koch snowflakes, Sierpinski triangles. The lesson 36 challenge is the Recursion Lab — five problems requiring genuinely recursive thinking.
Homework
4 minAdd three more problems to your olympics.py, all functional:
- FizzBuzz — list of strings for 1..20:
"Fizz"for multiples of 3,"Buzz"for 5,"FizzBuzz"for both, otherwise the number as a string. One comprehension. - Unique vowels in a list of words — given
["hello", "world"], return the set{'e', 'o'}. One set comprehension with two for-clauses. - Sliding-window pairs — given
[1, 2, 3, 4], return[(1, 2), (2, 3), (3, 4)]. Generator + comprehension.
Sample · three extras
# 1 — FizzBuzz def fizzbuzz(n): return [ "FizzBuzz" if i % 15 == 0 else "Fizz" if i % 3 == 0 else "Buzz" if i % 5 == 0 else str(i) for i in range(1, n + 1) ] # 2 — Unique vowels in any of the words def vowels_in(words): return {ch for w in words for ch in w if ch in "aeiou"} # 3 — Sliding window def pairs(seq): return [(seq[i], seq[i + 1]) for i in range(len(seq) - 1)] # Or as a generator: def pair_gen(seq): prev = None for x in seq: if prev is not None: yield (prev, x) prev = x assert fizzbuzz(15)[-1] == "FizzBuzz" assert vowels_in(["hello", "world"]) == {"e", "o"} assert pairs([1, 2, 3, 4]) == [(1, 2), (2, 3), (3, 4)]
Non-negotiables: chained conditional expression for FizzBuzz, two-for-clause set comprehension for vowels, indexed comprehension for sliding window. Each one short, readable, and functional.