Skip to content
  • itertools Deep Dive · Python

itertools Deep Dive

LearnViaNotes @learnvianotes· 4 min read

itertools is a toolkit of fast, memory-efficient iterator building blocks written in C. Combine them to express loops as pipelines.

Infinite iterators

Python
from itertools import count, cycle, repeat, islice

print(list(islice(count(10, 5), 4)))          # [10, 15, 20, 25]
print(list(islice(cycle("AB"), 5)))           # ['A', 'B', 'A', 'B', 'A']
print(list(repeat("x", 3)))                   # ['x', 'x', 'x']
print(list(map(pow, [2, 3, 4], repeat(2))))   # [4, 9, 16]

Slicing & filtering

Python
from itertools import islice, takewhile, dropwhile, filterfalse, compress

nums = [1, 4, 6, 3, 8, 2]
print(list(islice(nums, 1, 5, 2)))                  # [4, 3]
print(list(takewhile(lambda n: n < 5, nums)))       # [1, 4]
print(list(dropwhile(lambda n: n < 5, nums)))       # [6, 3, 8, 2]
print(list(filterfalse(lambda n: n % 2, nums)))     # [4, 6, 8, 2]
print(list(compress("abcdef", [1, 0, 1, 0, 0, 1]))) # ['a', 'c', 'f']

Combining & reshaping

Python
from itertools import chain, zip_longest, pairwise, batched, starmap, accumulate
import operator

print(list(chain.from_iterable([[1, 2], [3], []])))       # [1, 2, 3]
print(list(zip_longest("abc", [1], fillvalue=0)))          # [('a', 1), ('b', 0), ('c', 0)]
print(list(pairwise([1, 4, 9, 16])))                       # [(1, 4), (4, 9), (9, 16)]
print(list(batched(range(7), 3)))                          # [(0, 1, 2), (3, 4, 5), (6,)]
print(list(starmap(operator.mul, [(2, 3), (4, 5)])))       # [6, 20]
print(list(accumulate([3, 1, 4, 1, 5], max)))              # [3, 3, 4, 4, 5]
print(list(accumulate([1, 2, 3], initial=100)))            # [100, 101, 103, 106]
FunctionSinceUse it for
chain.from_iterable(xss)—Flatten one level lazily
zip_longest(a, b, fillvalue=…)—Zip without truncating to the shortest
pairwise(it)3.10Consecutive pairs — differences, gaps, edges of a path
batched(it, n)3.12Fixed-size chunks (strict=True in 3.13 demands full batches)
accumulate(it, func)—Running totals / max / products (prefix sums)
starmap(f, pairs)—f(*args) for each tuple of args
tee(it, n)—Split one iterator into n independent ones (buffers items)

Combinatorics

Python
from itertools import product, permutations, combinations, combinations_with_replacement
from math import comb, perm

print(list(product("ab", repeat=2)))               # [('a', 'a'), ('a', 'b'), ('b', 'a'), ('b', 'b')]
print(list(permutations("abc", 2)))                # [('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')]
print(list(combinations([1, 2, 3], 2)))            # [(1, 2), (1, 3), (2, 3)]
print(list(combinations_with_replacement("ab", 2)))  # [('a', 'a'), ('a', 'b'), ('b', 'b')]
print(len(list(permutations(range(5), 3))), perm(5, 3), comb(5, 3))   # 60 60 10
FunctionOrder matters?Repeats?Count for n items, size r
product(xs, repeat=r)YesYesnʳ
permutations(xs, r)YesNon! / (n−r)!
combinations(xs, r)NoNon! / (r!(n−r)!)
combinations_with_replacement(xs, r)NoYes(n+r−1)! / (r!(n−1)!)

groupby — done right

Python
from itertools import groupby
from operator import itemgetter

sales = [("fruit", "apple"), ("veg", "kale"), ("fruit", "kiwi"), ("veg", "leek")]
print([k for k, _ in groupby(sales, key=itemgetter(0))])   # ['fruit', 'veg', 'fruit', 'veg']

sales.sort(key=itemgetter(0))                               # sort by the SAME key first
grouped = {k: [name for _, name in g] for k, g in groupby(sales, key=itemgetter(0))}
print(grouped)                                              # {'fruit': ['apple', 'kiwi'], 'veg': ['kale', 'leek']}

# Run-length encoding: the classic groupby interview question
print("".join(f"{ch}{len(list(g))}" for ch, g in groupby("aaabccdd")))   # a3b1c2d2

Recipes

Python
from itertools import islice, chain, cycle
from collections import deque

def sliding_window(it, n):
    it = iter(it)
    window = deque(islice(it, n - 1), maxlen=n)
    for x in it:
        window.append(x)
        yield tuple(window)

def unique_everseen(it):
    seen = set()
    for x in it:
        if x not in seen:
            seen.add(x)
            yield x

def roundrobin(*its):
    iterators = cycle(iter(i) for i in its)
    pending = len(its)
    while pending:
        try:
            yield next(next(iterators))
        except StopIteration:
            pending -= 1
            iterators = cycle(list(islice(iterators, pending)))

print(list(sliding_window([1, 2, 3, 4, 5], 3)))   # [(1, 2, 3), (2, 3, 4), (3, 4, 5)]
print(list(unique_everseen("abracadabra")))       # ['a', 'b', 'r', 'c', 'd']
print(list(roundrobin("ABC", "D", "EF")))         # ['A', 'D', 'E', 'B', 'F', 'C']

How would you generate all subsets of a list?

Why does groupby return the same key more than once?

What is the benefit of itertools over writing loops?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report