Python · Syntax · Intermediate
itertools
Master Python's itertools module: tools for working with iterators, generating sequences, and solving combinatorics problems efficiently.
Quick topic start and explanations before exercises (exercises below):
Combinatorics with itertools
#itertools Reference
#Exercises:
Flatten Nested List
#Write a function `flatten(nested)` that takes a list of lists and returns a flat list using `itertools.chain.from_iterable`. Example: `flatten([[1, 2], [3], [4, 5, 6]])` → `[1, 2, 3, 4, 5, 6]`.
from itertools import chain
def flatten(nested):
pass
print(flatten([[1, 2], [3], [4, 5, 6]]))
# [1, 2, 3, 4, 5, 6]
Solution
from itertools import chain
def flatten(nested):
return list(chain.from_iterable(nested))
print(flatten([[1, 2], [3], [4, 5, 6]]))
# [1, 2, 3, 4, 5, 6]
First N from Generator
#Write a function `first_n(gen, n)` that returns the first `n` elements from any generator or iterator as a list, using `itertools.islice`. Example: `first_n((x**2 for x in range(100)), 5)` → `[0, 1, 4, 9, 16]`.
from itertools import islice
def first_n(gen, n):
pass
print(first_n((x**2 for x in range(100)), 5))
# [0, 1, 4, 9, 16]
Solution
from itertools import islice
def first_n(gen, n):
return list(islice(gen, n))
print(first_n((x**2 for x in range(100)), 5))
# [0, 1, 4, 9, 16]
Round-Robin Scheduler
#Write a function `round_robin(tasks)` that takes a list of task names and returns an infinite cycle iterator over them using `itertools.cycle`. Then use `itertools.islice` to extract the first 8 assignments. Example: `list(islice(round_robin(["A", "B", "C"]), 8))` → `["A", "B", "C", "A", "B", "C", "A", "B"]`.
from itertools import cycle, islice
def round_robin(tasks):
pass
print(list(islice(round_robin(["A", "B", "C"]), 8)))
# ["A", "B", "C", "A", "B", "C", "A", "B"]
Solution
from itertools import cycle, islice
def round_robin(tasks):
return cycle(tasks)
print(list(islice(round_robin(["A", "B", "C"]), 8)))
# ["A", "B", "C", "A", "B", "C", "A", "B"]
Merge Unequal Sequences
#Write a function `merge_sequences(seq1, seq2, seq3, fill=None)` that combines three sequences element by element using `itertools.zip_longest`, with `fill` as the missing value. Return the result as a list of tuples. Example: `merge_sequences([1,2,3], ["a","b"], [True], fill=0)` → `[(1,"a",True), (2,"b",0), (3,0,0)]`.
from itertools import zip_longest
def merge_sequences(seq1, seq2, seq3, fill=None):
pass
print(merge_sequences([1, 2, 3], ["a", "b"], [True], fill=0))
# [(1, "a", True), (2, "b", 0), (3, 0, 0)]
Solution
from itertools import zip_longest
def merge_sequences(seq1, seq2, seq3, fill=None):
return list(zip_longest(seq1, seq2, seq3, fillvalue=fill))
print(merge_sequences([1, 2, 3], ["a", "b"], [True], fill=0))
# [(1, "a", True), (2, "b", 0), (3, 0, 0)]
Running Maximum
#Write a function `running_max(numbers)` that returns a list where each element is the maximum value seen so far in `numbers`. Use `itertools.accumulate`. Example: `running_max([3, 1, 4, 1, 5, 9, 2, 6])` → `[3, 3, 4, 4, 5, 9, 9, 9]`.
from itertools import accumulate
import operator
def running_max(numbers):
pass
print(running_max([3, 1, 4, 1, 5, 9, 2, 6]))
# [3, 3, 4, 4, 5, 9, 9, 9]
Solution
from itertools import accumulate
import operator
def running_max(numbers):
return list(accumulate(numbers, func=max))
print(running_max([3, 1, 4, 1, 5, 9, 2, 6]))
# [3, 3, 4, 4, 5, 9, 9, 9]
Cartesian Product Grid
#Write a function `grid(rows, cols)` that returns all (row, col) coordinate pairs for a grid of given dimensions, using `itertools.product`. Example: `grid(2, 3)` → `[(0,0), (0,1), (0,2), (1,0), (1,1), (1,2)]`.
from itertools import product
def grid(rows, cols):
pass
print(grid(2, 3))
# [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2)]
Solution
from itertools import product
def grid(rows, cols):
return list(product(range(rows), range(cols)))
print(grid(2, 3))
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
All Pairs (Combinations)
#Write a function `all_pairs(items)` that returns all unique unordered pairs of items (no item paired with itself). Use `itertools.combinations`. Example: `all_pairs(["A", "B", "C", "D"])` → `[("A","B"), ("A","C"), ("A","D"), ("B","C"), ("B","D"), ("C","D")]`.
from itertools import combinations
def all_pairs(items):
pass
print(all_pairs(["A", "B", "C", "D"]))
# [("A","B"), ("A","C"), ("A","D"), ("B","C"), ("B","D"), ("C","D")]
Solution
from itertools import combinations
def all_pairs(items):
return list(combinations(items, 2))
print(all_pairs(["A", "B", "C", "D"]))
# [("A", "B"), ("A", "C"), ("A", "D"), ("B", "C"), ("B", "D"), ("C", "D")]
Group by Category
#Write a function `group_by(items, key_func)` that groups a list of items by a key function and returns a dict mapping each key to a list of items. Use `itertools.groupby` (remember to sort first). Example: with `items = ["apple", "ant", "banana", "bear", "cherry"]` and `key_func = lambda w: w[0]`, result is `{"a": ["ant", "apple"], "b": ["banana", "bear"], "c": ["cherry"]}`.
from itertools import groupby
def group_by(items, key_func):
pass
words = ["apple", "ant", "banana", "bear", "cherry"]
print(group_by(words, lambda w: w[0]))
# {"a": ["ant", "apple"], "b": ["banana", "bear"], "c": ["cherry"]}
Solution
from itertools import groupby
def group_by(items, key_func):
sorted_items = sorted(items, key=key_func)
result = {}
for key, group in groupby(sorted_items, key=key_func):
result[key] = list(group)
return result
words = ["apple", "ant", "banana", "bear", "cherry"]
print(group_by(words, lambda w: w[0]))
# {"a": ["ant", "apple"], "b": ["banana", "bear"], "c": ["cherry"]}
takewhile: Read Until Sentinel
#Write a function `read_until_empty(lines)` that takes a list of strings and returns only the lines before the first empty string (or line containing only whitespace), using `itertools.takewhile`. Example: `read_until_empty(["hello", "world", "", "more", "text"])` → `["hello", "world"]`.
from itertools import takewhile
def read_until_empty(lines):
pass
print(read_until_empty(["hello", "world", "", "more", "text"]))
# ["hello", "world"]
Solution
from itertools import takewhile
def read_until_empty(lines):
return list(takewhile(lambda line: line.strip() != "", lines))
print(read_until_empty(["hello", "world", "", "more", "text"]))
# ["hello", "world"]
Password Generator
#Write a function `generate_passwords(chars, length, count)` that generates `count` unique random passwords of `length` characters, where each password is built from `itertools.product(chars, repeat=length)`. Return the first `count` products as joined strings. Example: `generate_passwords("ab", 2, 4)` → `["aa", "ab", "ba", "bb"]` (use product in order, no randomness needed).
from itertools import product, islice
def generate_passwords(chars, length, count):
pass
print(generate_passwords("ab", 2, 4))
# ["aa", "ab", "ba", "bb"]
Solution
from itertools import product, islice
def generate_passwords(chars, length, count):
return ["".join(p) for p in islice(product(chars, repeat=length), count)]
print(generate_passwords("ab", 2, 4))
# ["aa", "ab", "ba", "bb"]