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]