ᚱᛗ
© 2022
Powered by Hugo

Comprehensions

Table of Contents

Motivating Comprehensions

Regular for loops

# cubing a list of integers
integers = [-4, -3, -2, -1, 0, 1, 2, 3, 4]
cubes = []
for i in integers:
    cubes.append(i ** 3)

print(cubes)  # [-64, -27, -8, -1, 0, 1, 8, 27, 64]
# adding a conditional (use only positive bases)
pos_cubes = []
for i in integers:
    if i > 0:
        pos_cubes.append(i ** 3)

print(pos_cubes)  # [1, 8, 27, 64]

Built-in map() and filter()

# map
map_cubes = map(lambda x: x ** 3, integers)
map_cubes = list(map_cubes)
print(map_cubes)  # [-64, -27, -8, -1, 0, 1, 8, 27, 64]
assert map_cubes == cubes
# map & filter
map_pos_cubes = map(lambda x: x ** 3, filter(lambda x: x > 0, integers))
map_pos_cubes = list(map_pos_cubes)
print(map_pos_cubes)  # [1, 8, 27, 64]
assert map_pos_cubes == pos_cubes

Note: even with simple lambdas, the expression is not very readable and requires too much boilerplate.

Enter List Comprehensions

# appreciate the readability
lc_cubes = [i ** 3 for i in integers]
print(lc_cubes)  # [-64, -27, -8, -1, 0, 1, 8, 27, 64]
assert map_cubes == cubes == lc_cubes

List Comprehension with Conditionals

lc_pos_cubes = [i ** 3 for i in integers if i > 0]
print(lc_pos_cubes)  # [1, 8, 27, 64]
assert map_pos_cubes == pos_cubes == lc_pos_cubes

Two-dimensional List Comprehension

# motivation
matrix = [[9, 8, 7], [6, 5, 4], [3, 2, 1, 0]]
unrolled = []
for row in matrix:
    for scalar in row:
        unrolled.append(scalar)
print(unrolled)  # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
# list comprehension approach
lc_unrolled = [scalar for row in matrix for scalar in row]
print(lc_unrolled)  # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
assert unrolled == lc_unrolled
# unroll only even entries
lc_e_unrolled = [scalar for row in matrix for scalar in row if scalar % 2 == 0]
print(lc_e_unrolled)  # [8, 6, 4, 2, 0]

Note: list comprehensions allow for succinct and readable expressions, up to a point. Even though it is syntactically possible to add more than two for sub-expressions and one extra conditional per for sub-expression, at that point readability goes down substantially. For such cases a traditional for loop is preferable.

Dictionary Comprehensions

From an Iterable

days = ["måndag", "tisdag", "onsdag", "torsdag", "fredag", "lördag", "söndag"]
dict_days = {k: v[:3] for k, v in enumerate(days)}
print(dict_days)
# {0: 'mån', 1: 'tis', 2: 'ons', 3: 'tor', 4: 'fre', 5: 'lör', 6: 'sön'}

Build On the Fly

# dictionary with x square
dict_squares = {x: x ** 2 for x in range(7)}
print(dict_squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36}

Set Comprehensions

# only cube even numbers and return them in a set
items = [3, 3, 3, 2, 2, 2, 1, 1, 1, 4, 4, 4]
cubed_set_evens = {x ** 3 for x in items if x % 2 == 0}
print(cubed_set_evens)  # {8, 64}