1.2.34 · D4Introduction to Programming (Python)

Exercises — List comprehensions — `[expr for x in iterable if condition]`

2,541 words12 min readBack to topic

Below, one picture shows the "conveyor belt" mental model we reuse — items flow left, the filter drops some, the transform reshapes the survivors into a new list.

Figure — List comprehensions — `[expr for x in iterable if condition]`

Level 1 — Recognition

You only need to read the template and match the four slots: expr, for x in iterable, and the optional if condition.

L1.1

Recall Solution L1.1
  • expr = w.upper() — what goes into the new list.
  • iterable = ["hi", "yo"] — where each w comes from.
  • condition = len(w) == 2 — keep w only if this is True.

Both words have length 2, so both survive → ["HI", "YO"].

L1.2

Recall Solution L1.2

No if, so every item is kept. We add 1 to each:

Answer: [11, 21, 31].

L1.3

Recall Solution L1.3

Here expr is just n, so survivors pass through unchanged. The test n > 5 keeps 7 and 9, drops 4 and 2.

Answer: [7, 9].


Level 2 — Application

Now you produce a comprehension or its exact output, including sign/edge behaviour.

L2.1

Recall Solution L2.1

Odd means x % 2 == 1 (the remainder after dividing by 2 is 1).

[x**2 for x in range(6) if x % 2 == 1]

range(6) is 0,1,2,3,4,5. Odds are 1, 3, 5:

Answer: [1, 9, 25].

L2.2

Recall Solution L2.2

We are choosing a value, not dropping items, so we use a conditional expression (a if cond else b) which lives before the for:

[n if n >= 0 else 0 for n in [3, -1, -4, 5]]
  • → keep 3
  • ? No → 0
  • ? No → 0
  • → keep 5

Answer: [3, 0, 0, 5]. (See Conditional expressions (ternary).)

L2.3

Recall Solution L2.3

(a) The iterable is empty, so the loop body never runs → []. (b) Five items are considered, none passes x > 100, so all are filtered out → []. (c) range(0) produces no numbers at all → [].

Lesson: an empty result happens in two distinct ways — an empty source, or a filter that rejects everything. Both are legal, neither is an error.


Level 3 — Analysis

You must trace nested logic and reason about ordering and quantity.

L3.1

Recall Solution L3.1

Two fors read outer-to-inner, top-to-bottom — identical to nested loops:

for row in grid:      # outer (leftmost for)
    for cell in row:  # inner
        result.append(cell)

Row by row: [1,2,3]1,2,3; [4]4; [] → (nothing); [5,6]5,6.

Answer: [1, 2, 3, 4, 5, 6], which has 6 elements. The empty inner list contributes nothing — just like an inner loop over an empty list runs zero times.

L3.2

Recall Solution L3.2

Outer i runs 0,1,2; for each, inner j runs 0,1,2; keep only pairs with i < j.

  • i=0: j=1→(0,1), j=2→(0,2) (j=0 fails 0<0)
  • i=1: j=2→(1,2) (j=0,1 fail)
  • i=2: no j satisfies 2 < j

Answer: [(0, 1), (0, 2), (1, 2)] — exactly the 3 "strictly increasing" pairs.

L3.3

Recall Solution L3.3

By execution order the inner for y in range(x) binds y before the filter if y > 0 runs, so y is always defined when tested. Trace it:

  • x=0: range(0) is empty → no y.
  • x=1: range(1) = 0y=0, 0 > 0 False → drop.
  • x=2: range(2) = 0,1y=0 dropped, y=1 kept.

Answer: [1]. The rule: a filter if can use any variable already bound by a for to its left.


Level 4 — Synthesis

Combine transform + filter + nesting, and choose the right tool.

L4.1

Recall Solution L4.1

"Divisible by 3" means x % 3 == 0. "Cube" means x**3.

[x**3 for x in range(1, 11) if x % 3 == 0]

range(1, 11) = 1..10; multiples of 3 are 3, 6, 9:

Answer: [27, 216, 729].

L4.2

Recall Solution L4.2

Two ifs working together — filter if (after for) drops odds; ternary (before for) picks the label:

["big" if n > 4 else "small" for n in [-5, -2, 0, 3, 8] if n % 2 == 0]

Evens are -2, 0, 8 (note: -5 % 2 == 1, 3 % 2 == 1, so both odd → dropped).

  • -2 > 4? No → "small"
  • 0 > 4? No → "small"
  • 8 > 4? Yes → "big"

Answer: ["small", "small", "big"].

L4.3

Recall Solution L4.3

Comprehension (one English sentence):

[len(w) for w in ["cat", "dog", "banana", "sky"] if "a" in w]

Words with "a": "cat" (3), "banana" (6). → [3, 6].

Functional equivalent:

list(map(len, filter(lambda w: "a" in w, ["cat","dog","banana","sky"])))

Same result [3, 6], but the comprehension reads left-to-right as "give me the length for each word if it contains a" — clearer than nesting map(filter(...)) inside-out. Prefer the comprehension here.


Level 5 — Mastery

Build something a real program would use; reason about equivalence and laziness.

L5.1

Recall Solution L5.1

Main diagonal is (0,0), (1,1), (2,2). We nest two fors and filter them out with r != c:

[(r, c) for r in range(3) for c in range(3) if r != c]

All 9 pairs minus the 3 diagonal ones = 6 pairs: [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)].

Sanity count: — matches the list length.

L5.2

Recall Solution L5.2

(a) A is a list — all 1000 squares are built and stored immediately. (b) B is a generator (see Generator expressions) — it stores a recipe, producing squares one at a time, on demand, using almost no memory. (c) . So sum(B) = 332833500. (d) A generator is exhausted after one full pass. The second sum(B) sees nothing left → 0. (A list A could be summed again and again; a generator cannot.)

L5.3

Recall Solution L5.3

Comprehension trace (forifexpr):

  • x=0: even → append 0
  • x=1: odd → skip
  • x=2: even → append 20
  • x=3: odd → skip

[0, 20].

Loop it desugars to:

r = []
for x in range(4):
    if x % 2 == 0:
        r.append(x * 10)
# r == [0, 20]

Identical output [0, 20]. This is the defining law: the comprehension is pure syntactic sugar for that loop — same picks, same filter, same appends, same list.


Recall One-line self-test recap

Filter if position ::: after the for (no else) — decides whether to include. Ternary if/else position ::: before the for, inside expr — decides what value. Nested for order ::: leftmost for is the outermost loop, read top-to-bottom. Two ways to get an empty result ::: empty source iterable, or a filter that rejects everything. Generator reused after full consumption ::: yields nothing — it is exhausted.

Connections

  • For loops — every solution above shows the equivalent loop.
  • Conditional expressions (ternary) — the before-for value chooser (L2.2, L4.2).
  • Dictionary and set comprehensions — same slot logic with {}.
  • Generator expressions — the lazy (...) version (L5.2).
  • map() and filter() — functional equivalents (L4.3).
  • range() and iterables — feeds the for x in iterable slot throughout.