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