IntermediatePython ยท Lesson 4

Comprehensions and Functional Tools

Master list/dict/set comprehensions, map, filter, reduce, and itertools for elegant Python code

List Comprehensions

# Basic form: [expression for item in iterable if condition]

# Simple transformation
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# Filtering
evens = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

# Nested loops
matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]
# [[1, 2, 3], [2, 4, 6], [3, 6, 9]]

# Flatten nested list
flat = [x for row in matrix for x in row]
# [1, 2, 3, 2, 4, 6, 3, 6, 9]

# Conditional expression
labels = ["even" if x % 2 == 0 else "odd" for x in range(6)]
# ['even', 'odd', 'even', 'odd', 'even', 'odd']

Dict and Set Comprehensions

# Dict comprehension
word_lengths = {word: len(word) for word in ["apple", "banana", "cherry"]}
# {'apple': 5, 'banana': 6, 'cherry': 6}

inverted = {v: k for k, v in {"a": 1, "b": 2}.items()}
# {1: 'a', 2: 'b'}

# Filtered dict
passing = {name: score for name, score in {"Alice": 85, "Bob": 60, "Carol": 92}.items()
           if score >= 70}
# {'Alice': 85, 'Carol': 92}

# Set comprehension
unique_lengths = {len(word) for word in ["cat", "bat", "dog", "elephant"]}
# {3, 8}

Generator Expressions

# Like list comprehensions but lazy โ€” no memory cost
gen = (x**2 for x in range(1_000_000))  # creates generator, not list
print(sum(gen))  # evaluated on demand

# Use in function calls
total = sum(x**2 for x in range(100))
any_negative = any(x < 0 for x in [1, 2, -3, 4])
all_positive = all(x > 0 for x in [1, 2, 3, 4])

# As argument (no extra parens when only arg)
print(max(len(w) for w in ["cat", "python", "hi"]))  # 6

map() and filter()

nums = [1, 2, 3, 4, 5]

# map โ€” apply function to all elements
doubled = list(map(lambda x: x * 2, nums))  # [2, 4, 6, 8, 10]
str_nums = list(map(str, nums))              # ['1', '2', '3', '4', '5']

# Better: list comprehension usually cleaner
doubled = [x * 2 for x in nums]

# filter โ€” keep elements where function returns True
evens = list(filter(lambda x: x % 2 == 0, nums))  # [2, 4]
# Better:
evens = [x for x in nums if x % 2 == 0]

# map with multiple iterables
result = list(map(lambda a, b: a + b, [1, 2, 3], [10, 20, 30]))
# [11, 22, 33]

functools.reduce()

from functools import reduce

nums = [1, 2, 3, 4, 5]

product = reduce(lambda acc, x: acc * x, nums)    # 120
total   = reduce(lambda acc, x: acc + x, nums, 0) # 15 (with initial value)

# Find maximum:
max_val = reduce(lambda a, b: a if a > b else b, nums)  # 5

itertools โ€” Powerful Iteration Tools

import itertools

# count โ€” infinite counter
for i in itertools.count(start=10, step=2):
    if i > 20:
        break
    print(i)  # 10, 12, 14, 16, 18, 20

# cycle โ€” repeat sequence
colors = itertools.cycle(["red", "green", "blue"])
for i, color in zip(range(6), colors):
    print(f"Item {i}: {color}")

# repeat โ€” repeat a value n times
list(itertools.repeat(5, 3))  # [5, 5, 5]

# chain โ€” concatenate iterables
combined = list(itertools.chain([1, 2], [3, 4], [5]))
# [1, 2, 3, 4, 5]

# zip_longest
from itertools import zip_longest
list(zip_longest([1, 2, 3], ["a", "b"], fillvalue=None))
# [(1, 'a'), (2, 'b'), (3, None)]

# combinations and permutations
list(itertools.combinations("ABC", 2))    # [('A','B'), ('A','C'), ('B','C')]
list(itertools.permutations("ABC", 2))    # [('A','B'), ('A','C'), ('B','A'), ...]
list(itertools.combinations_with_replacement("AB", 2))  # [('A','A'), ('A','B'), ('B','B')]

# product โ€” Cartesian product
list(itertools.product([0, 1], repeat=3))  # all 3-bit binary numbers
# [(0,0,0), (0,0,1), (0,1,0), ...]

# groupby โ€” group consecutive elements
data = [("A", 1), ("A", 2), ("B", 3), ("B", 4), ("C", 5)]
for key, group in itertools.groupby(data, key=lambda x: x[0]):
    print(key, list(group))

# islice โ€” lazy slicing
first_5_squares = list(itertools.islice((x**2 for x in itertools.count()), 5))
# [0, 1, 4, 9, 16]

# takewhile / dropwhile
list(itertools.takewhile(lambda x: x < 5, [1, 3, 5, 2, 4]))  # [1, 3]
list(itertools.dropwhile(lambda x: x < 5, [1, 3, 5, 2, 4]))  # [5, 2, 4]

Exercises

Exercise 1: Flatten Deeply Nested List

Flatten an arbitrarily nested list using a comprehension/generator.

Solution:

def flatten(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item

data = [1, [2, [3, [4, 5]], 6], 7]
print(list(flatten(data)))  # [1, 2, 3, 4, 5, 6, 7]

Exercise 2: Running Statistics

Use itertools.accumulate to compute running totals, maxima.

Solution:

import itertools

nums = [3, 1, 4, 1, 5, 9, 2, 6]
running_total = list(itertools.accumulate(nums))           # sums
running_max   = list(itertools.accumulate(nums, max))      # running max
print(running_total)  # [3, 4, 8, 9, 14, 23, 25, 31]
print(running_max)    # [3, 3, 4, 4, 5, 9, 9, 9]

Exercise 3: Sliding Window Average

Using itertools, compute the average of every window of size k.

Solution:

import itertools

def sliding_avg(data, k):
    window_sums = itertools.accumulate(data)
    # Actually, use zip approach:
    from collections import deque
    dq = deque(maxlen=k)
    for i, val in enumerate(data):
        dq.append(val)
        if i >= k - 1:
            yield sum(dq) / k

print(list(sliding_avg([1, 2, 3, 4, 5, 6], 3)))
# [2.0, 3.0, 4.0, 5.0]