itertools essentials
Overview
`itertools` is a toolbox of memory-efficient, lazy building blocks for looping. Each returns an iterator that yields on demand rather than materializing a list. The high-value few: import itertools as it it.chain([1,2], [3,4]) # 1 2 3 4 — flatten iterables end-to-end it.islice(range(100), 2, 5) # 2 3 4 — slice an iterator (can't do seq[2:5] on one) it.groupby('aabbbc') # (a,[aa]) (b,[bbb]) (c,[c]) — group CONSECUTIVE runs it.product([0,1], repeat=2) # (0,0)(0,1)(1,0)(1,1) — cartesian product it.combinations('abc', 2) # ab ac bc — choose-k, order-independent it.permutations('abc', 2) # ab ac ba bc ca cb — ordered arrangements The critical `groupby` gotcha: it groups only *consecutive* equal keys, so you almost always `sorted()` first if you want global grouping. Because results are lazy iterators, you consume them once — wrap in `list()` to reuse.
These express common combinatorial and streaming loops declaratively and without building big intermediate lists. `product`/`combinations`/`permutations` map directly onto brute-force and enumeration interview problems.
Where used: Flattening nested iterables, Generating combinations/permutations for brute force, Streaming large data without materializing lists
Why learn this
- combinations/permutations/product turn fiddly nested-loop enumeration into a single clear call.
- Lazy tools like chain and islice process large or infinite streams without building huge lists in memory.
Code walkthrough
import itertools
data = 'aaabbc'
for key, group in itertools.groupby(data):
print(key, len(list(group)))
Focus: groupby splits into consecutive runs: a x3, b x2, c x1
Common mistakes
- Expecting groupby to group non-adjacent items: `groupby` only groups *consecutive* equal keys. On unsorted data `groupby([1,2,1])` yields three groups, not two. Sort by the same key first if you want all like items grouped together.
- Reusing an exhausted iterator: itertools results are one-shot iterators. After you loop over `chain(...)` once it's empty; a second loop yields nothing. Wrap in `list(...)` if you need to iterate more than once.
- Confusing combinations with permutations: `combinations` ignores order (ab == ba, so only `ab`); `permutations` counts order (`ab` and `ba` both appear). Picking the wrong one gives too few or too many results.
Glossary
- lazy iterator
- An object that produces items one at a time on demand rather than building the whole sequence in memory. itertools tools return these.
Recall questions
- What is the key constraint of `itertools.groupby`?
- What is the difference between `combinations` and `permutations`?
- Why can't you slice an iterator with `[2:5]`, and what do you use instead?
Understanding checks
What list of keys does this print?
[1, 2, 1]
groupby breaks on *consecutive* runs, not global value. The two 1s at the start form one group, then 2, then the trailing 1 is a separate group — so 1 appears twice. Sorting first would give [1, 2].
What are the two printed counts?
6 and 12
combinations of 4 items taken 2 = C(4,2) = 6 (order ignored). permutations = P(4,2) = 12 (order matters), exactly twice as many because each pair counts in both orders.
Practice tasks
All pairs that sum to a target
Use `itertools.combinations` to print every unordered pair of numbers from the list that adds up to 6.