Skip to content

functools and itertools, Part 2: itertools

← Part 1: functools · Back to Overview

Learn Your Way

Read Build Watch Test Review Visualize
You are here Projects — — Flashcards —

The itertools module gives you building blocks for efficient iteration. These tools let you chain sequences, generate combinations, group data, and work with infinite iterators, all lazily and memory-efficiently.

itertools.chain: combine multiple iterables

from itertools import chain

a = [1, 2, 3]
b = [4, 5]
c = [6, 7, 8, 9]

list(chain(a, b, c))    # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Flatten a list of lists:
nested = [[1, 2], [3], [4, 5, 6]]
list(chain.from_iterable(nested))    # [1, 2, 3, 4, 5, 6]

itertools.groupby: group consecutive items

Groups items that have the same key. Important: the data must be sorted by the key first.

from itertools import groupby

# Group words by their first letter:
words = ["apple", "avocado", "banana", "blueberry", "cherry"]
# Already sorted by first letter!

for letter, group in groupby(words, key=lambda w: w[0]):
    print(f"{letter}: {list(group)}")
# a: ['apple', 'avocado']
# b: ['banana', 'blueberry']
# c: ['cherry']

Real-world example: group log entries by date:

from itertools import groupby

logs = [
    {"date": "2024-01-15", "msg": "User login"},
    {"date": "2024-01-15", "msg": "File upload"},
    {"date": "2024-01-16", "msg": "User logout"},
    {"date": "2024-01-16", "msg": "Error 500"},
]

for date, entries in groupby(logs, key=lambda e: e["date"]):
    print(f"\n{date}:")
    for entry in entries:
        print(f"  {entry['msg']}")

itertools.product: cartesian product

All combinations of items from multiple iterables:

from itertools import product

colors = ["red", "blue"]
sizes = ["S", "M", "L"]

for color, size in product(colors, sizes):
    print(f"{color}-{size}")
# red-S, red-M, red-L, blue-S, blue-M, blue-L

itertools.combinations and permutations

from itertools import combinations, permutations

# Combinations — order does not matter, no repeats:
list(combinations("ABCD", 2))
# [('A','B'), ('A','C'), ('A','D'), ('B','C'), ('B','D'), ('C','D')]

# Permutations — order matters:
list(permutations("ABC", 2))
# [('A','B'), ('A','C'), ('B','A'), ('B','C'), ('C','A'), ('C','B')]

itertools.islice: slice a generator

You cannot use [start:stop] on generators. Use islice instead:

from itertools import islice

def infinite_counter():
    n = 0
    while True:
        yield n
        n += 1

# Get the first 5 values:
list(islice(infinite_counter(), 5))    # [0, 1, 2, 3, 4]

# Skip 10, take 5:
list(islice(infinite_counter(), 10, 15))    # [10, 11, 12, 13, 14]

itertools.count, cycle, repeat: infinite iterators

from itertools import count, cycle, repeat

# count — infinite counter:
for i in count(start=10, step=3):
    if i > 20:
        break
    print(i)    # 10, 13, 16, 19

# cycle — repeat an iterable forever:
colors = cycle(["red", "green", "blue"])
for _, color in zip(range(6), colors):
    print(color)    # red, green, blue, red, green, blue

# repeat — yield the same value:
list(repeat("hello", 3))    # ["hello", "hello", "hello"]

itertools.starmap: map with unpacking

from itertools import starmap

pairs = [(2, 3), (4, 5), (6, 7)]

# Without starmap:
results = [pow(a, b) for a, b in pairs]

# With starmap:
list(starmap(pow, pairs))    # [8, 1024, 279936]

Combining functools and itertools

from itertools import combinations

# Find all pairs of numbers that sum to a target:
def find_pairs(numbers, target):
    return [
        pair for pair in combinations(numbers, 2)
        if sum(pair) == target
    ]

find_pairs([1, 2, 3, 4, 5], 6)    # [(1, 5), (2, 4)]

Common Mistakes (itertools)

Using groupby on unsorted data:

# WRONG — data is not sorted by key:
data = [1, 2, 1, 3, 2]
for key, group in groupby(data):
    print(key, list(group))
# 1 [1]
# 2 [2]
# 1 [1]    <- 1 appears twice because it was not consecutive!
# 3 [3]
# 2 [2]

# RIGHT — sort first:
data = sorted([1, 2, 1, 3, 2])
for key, group in groupby(data):
    print(key, list(group))
# 1 [1, 1]
# 2 [2, 2]
# 3 [3]

Consuming a groupby group after moving to the next:

# WRONG — the group iterator is consumed when you advance to the next group
groups = []
for key, group in groupby(sorted_data, key_func):
    groups.append((key, group))    # group is exhausted by the next iteration!

# RIGHT — materialize the group immediately:
groups = [(key, list(group)) for key, group in groupby(sorted_data, key_func)]


← Part 1: functools Overview