1.2.32 · D4Introduction to Programming (Python)

Exercises — Lambda functions — anonymous, used with map - filter

2,764 words13 min readBack to topic

Before we start, one mental picture you will reuse everywhere on this page: think of your data as a conveyor belt of items, and a lambda as a sticky-note rule you hand to a worker. map is a worker who reshapes every item; filter is a bouncer who keeps or tosses each item. Look at the figure below and keep it in mind.

Figure — Lambda functions — anonymous, used with map - filter

Level 1 — Recognition

Exercise 1.1

What is the value of the expression lambda x: x + 1? Choose: (a) the number 1, (b) a function object, (c) a syntax error, (d) None.

Recall Solution

(b) a function object. A lambda is an expression that evaluates to a function. Writing it alone doesn't call it — it just creates the helper. Compare: writing 5 produces the number 5; writing lambda x: x+1 produces a callable. You could store it: f = lambda x: x + 1, then f(4) gives 5. This is exactly the first-class idea — functions are values you can hand around.

Exercise 1.2

Which of these is a SyntaxError? A = lambda x: return x B = lambda x: x C = lambda: 42

Recall Solution

A is a SyntaxError. A lambda body must be a single expression, and its value is automatically the return value. return is a statement, not an expression, so it is forbidden here. B is fine (identity). C is fine — a lambda may take zero arguments; (lambda: 42)() returns 42.

Exercise 1.3

True or false: map(f, it) returns a list.

Recall Solution

False. In Python 3, map returns a lazy iterator — it computes values on demand, one at a time, and is exhausted after one full pass. To see all results at once, wrap it: list(map(f, it)). This laziness is the topic of Iterators and lazy evaluation.


Level 2 — Application

Exercise 2.1

Evaluate: list(map(lambda x: x * 3, [0, 1, 4]))

Recall Solution

map applies the rule x * 3 to every element in order:

  • 0 * 3 = 0
  • 1 * 3 = 3
  • 4 * 3 = 12

Result: [0, 3, 12].

Exercise 2.2

Evaluate: list(filter(lambda x: x > 5, [3, 8, 5, 10, 1]))

Recall Solution

filter keeps each x only when the predicate is truthy (True), and drops the rest. Test each:

  • 3 > 5 → False → drop
  • 8 > 5 → True → keep
  • 5 > 5 → False → drop (5 is not strictly greater than 5)
  • 10 > 5 → True → keep
  • 1 > 5 → False → drop

Result: [8, 10].

Exercise 2.3

Evaluate: sorted(['pear', 'fig', 'apple'], key=lambda s: len(s))

Recall Solution

sorted needs one comparable value per item. Here key=lambda s: len(s) says "compare by string length":

  • 'pear' → 4
  • 'fig' → 3
  • 'apple' → 5

Sorting by those keys ascending (3, 4, 5) gives: ['fig', 'pear', 'apple']. Why is key called only once per item, not on every comparison? sorted uses a strategy called decorate–sort–undecorate. First it decorates: it runs your key function once on each element and pairs the result with the element, building an internal list like [(4,'pear'), (3,'fig'), (5,'apple')]. Then it sorts those pairs by their computed key. Finally it undecorates: it throws away the keys and returns just the elements in the new order. Because the keys are computed up front and stored, an item's key is never recomputed during the many pairwise comparisons a sort performs — so an expensive key (say a database lookup) runs n times, not n·log n times. See sorted and key functions.

Exercise 2.4

Evaluate: list(map(lambda a, b: a + b, [1, 2, 3], [10, 20, 30]))

Recall Solution

map can take multiple iterables; it pulls one item from each and passes them together to the two-argument lambda:

  • 1 + 10 = 11
  • 2 + 20 = 22
  • 3 + 30 = 33

Result: [11, 22, 33]. (If the iterables differ in length, map stops at the shortest.)


Level 3 — Analysis

Exercise 3.1

This code prints an empty list on the second line. Why?

result = map(lambda x: x * x, [1, 2, 3])
print(list(result))   # [1, 4, 9]
print(list(result))   # []  <-- why?!
Recall Solution

A map object is a one-pass iterator. The first list(result) walks it to the end, producing [1, 4, 9] and exhausting it. By the second list(result), the iterator is already standing at the end with nothing left — so it yields []. Fix: materialise once and reuse: squares = list(map(...)), then print squares twice.

Exercise 3.2

What does list(filter(lambda x: x, [0, 1, '', 'hi', None, [7], []])) return, and why?

Recall Solution

The predicate is just x itself, so filter keeps items whose truthiness is True. Recall the falsy values: 0, '', None, [], False, 0.0.

  • 0 → falsy → drop
  • 1 → truthy → keep
  • '' → falsy → drop
  • 'hi' → truthy → keep
  • None → falsy → drop
  • [7] → non-empty list → truthy → keep
  • [] → empty list → falsy → drop

Result: [1, 'hi', [7]]. This is the handy "strip the falsy values" idiom.

Exercise 3.3

Predict and explain:

list(map(lambda x: x * 2, filter(lambda x: x % 2 == 1, [1, 2, 3, 4, 5])))
Recall Solution

Read inside-out — the innermost call runs first.

  1. filter(lambda x: x % 2 == 1, ...) keeps the odd numbers: [1, 3, 5].
  2. map(lambda x: x * 2, ...) doubles each survivor: 2, 6, 10.

Result: [2, 6, 10]. Order matters: filtering first means map only works on 3 items, not 5.

Exercise 3.4

Find the bug. The author wanted a list of squares but got the wrong numbers (no error was raised) — explain the silent bug and fix it:

nums = [1, 2, 3]
squares = list(map(lambda x: x^2, nums))   # expected [1, 4, 9]
Recall Solution

The gotcha is that ^ in Python is bitwise XOR, not exponentiation. On integers XOR is perfectly legal, so no error is raised — the code runs and silently produces wrong values:

  • 1 ^ 2 = 3
  • 2 ^ 2 = 0
  • 3 ^ 2 = 1

So squares becomes [3, 0, 1] instead of [1, 4, 9] — a silent logic bug, the most dangerous kind, because nothing crashes to warn you. The core lesson: power in Python is **, not ^. Fix: lambda x: x ** 2[1, 4, 9].


Level 4 — Synthesis

Exercise 4.1

Build a single pipeline that takes words = ['cat', 'elephant', 'dog', 'hippopotamus', 'ox'] and produces the uppercased version of each word longer than 3 letters. Write it, then give the result.

Recall Solution

Filter first (thin the belt), then map (reshape survivors):

words = ['cat', 'elephant', 'dog', 'hippopotamus', 'ox']
out = list(map(lambda w: w.upper(),
               filter(lambda w: len(w) > 3, words)))
  • filter keeps words with len > 3: ['elephant', 'hippopotamus'].
  • map uppercases each: ['ELEPHANT', 'HIPPOPOTAMUS'].

Result: ['ELEPHANT', 'HIPPOPOTAMUS'].

Exercise 4.2

Sort a list of student records by score descending, then name ascending as tie-breaker. students = [('Ana', 88), ('Bo', 88), ('Cy', 91), ('Di', 75)]

Recall Solution

sorted compares tuples element by element. We want score descending but name ascending — opposite directions. Trick: negate the numeric key so "larger score" becomes "smaller sort key".

students = [('Ana', 88), ('Bo', 88), ('Cy', 91), ('Di', 75)]
out = sorted(students, key=lambda s: (-s[1], s[0]))

Keys computed: Ana→(-88,'Ana'), Bo→(-88,'Bo'), Cy→(-91,'Cy'), Di→(-75,'Di'). Ascending on those keys: Cy(-91), then the two -88s broken by name (Ana before Bo), then Di(-75).

Result: [('Cy', 91), ('Ana', 88), ('Bo', 88), ('Di', 75)].

Exercise 4.3

Given pairs = [(1, 3), (2, 2), (5, 1), (0, 9)], use map + a lambda to compute the product of each pair, then filter those products greater than 5.

Recall Solution
pairs = [(1, 3), (2, 2), (5, 1), (0, 9)]
products = map(lambda p: p[0] * p[1], pairs)      # 3, 4, 5, 0
out = list(filter(lambda n: n > 5, products))
  • Products: 1*3=3, 2*2=4, 5*1=5, 0*9=0[3, 4, 5, 0].
  • Keep > 5: none of 3, 4, 5, 0 exceed 5.

Result: [] (empty list). Note 5 > 5 is False — a classic off-by-boundary check.


Level 5 — Mastery

Exercise 5.1 — The infamous closure-in-a-loop bug

What does this print, and what did the author probably intend?

funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])
Recall Solution

It prints [2, 2, 2], not [0, 1, 2]. Each lambda closes over the variable i, not its value at creation time. By the time we call the functions, the loop has finished and i is stuck at its final value 2. All three lambdas look up the same i. Fix — bind the value now with a default argument (evaluated at definition time):

funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])   # [0, 1, 2]

This is a higher-order subtlety: functions capture names, and lambdas are no exception.

Exercise 5.2 — Laziness and side effects

How many times is spy printed, and why?

def spy(x):
    print('seen', x)
    return x * 10
 
gen = map(spy, [1, 2, 3])
first = next(gen)      # pull ONE value
print('first =', first)
Recall Solution

spy prints once (seen 1), then first = 10. map is lazy: next(gen) pulls exactly one item off the belt, so spy runs only for 1. The elements 2 and 3 are never touched because we never asked for them. If we later did list(gen), spy would fire for 2 and 3 — and note the 1 is already consumed, so it wouldn't reappear. Full output:

seen 1
first = 10

This demonstrates why lazy iterators can save work — untaken items cost nothing.

Exercise 5.3 — Design judgement

Rewrite this lambda-heavy line more readably, and say when you'd keep the lambda vs. switch to a def.

result = list(map(lambda x: 'even' if x % 2 == 0 else 'odd', range(4)))
Recall Solution

Output first: range(4) is 0,1,2,3['even', 'odd', 'even', 'odd']. A ternary (a if cond else b) is a single expression, so it's legal inside a lambda. But a list comprehension is usually clearer:

result = ['even' if x % 2 == 0 else 'odd' for x in range(4)]

Rule of thumb: keep the lambda when the rule is tiny and you're feeding a function-taking tool (map/filter/sorted(key=...)). Switch to a named def the moment the logic needs a name to explain itself, multiple statements, or reuse. A named def from Functions and def wins on readability whenever the "cleverness cost" outweighs the "one-liner benefit".


Recall Final mixed drill (predict all five, then reveal)
  1. list(map(lambda x: x + 1, [0, 9])) ::: [1, 10]
  2. list(filter(lambda x: x, [0, 2, '', 'a'])) ::: [2, 'a']
  3. sorted([-3, 1, -2], key=lambda x: abs(x)) ::: [1, -2, -3]
  4. list(map(lambda a, b: a * b, [2, 3], [4, 5])) ::: [8, 15]
  5. (lambda: 7)() ::: 7

Connections

  • Functions and def — when to graduate a lambda into a named function.
  • First-class functions — why a lambda can be stored, passed, and returned.
  • List comprehensions — the readable alternative for map/filter pipelines.
  • Iterators and lazy evaluation — the root of the one-pass and side-effect exercises.
  • sorted and key functions — the key= exercises above.
  • Higher-order functions — the closure-capture behaviour in L5.