Lesson 20 of 34 · 0 of 2 exercises solved
collections & itertools
Two standard- that turn ten-line into one-liners: collections for smarter containers, itertools for smarter iteration.
Counter counts things. It is a dict subclass, so counts["x"] works, missing keys read as 0, and most_common(n) hands you the leaderboard.
from collections import Counter
words = "red blue red green red blue yellow".split()
counts = Counter(words)
print(counts)
print(counts["red"], counts["nope"])
print(counts.most_common(2))
letters = Counter("mississippi")
print(letters.most_common(3))
print(Counter(a=3, b=1) + Counter(a=1, c=2))defaultdict creates a missing value on first access, which removes the "if key not in d" dance when grouping. deque is a double-ended with O(1) appends and pops at both ends — the right structure for sliding windows and breadth-first search.
from collections import defaultdict, deque
by_first_letter = defaultdict(list)
for name in ["Ada", "Alan", "Maya", "Mark", "Sam"]:
by_first_letter[name[0]].append(name)
print(dict(by_first_letter))
window = deque(maxlen=3)
for reading in [12, 15, 11, 18, 20]:
window.append(reading)
print(list(window), round(sum(window) / len(window), 1))
queue = deque(["email", "lunch", "review"])
queue.appendleft("standup")
print(queue.popleft(), queue.pop(), list(queue))namedtuple gives you lightweight records with named fields (use a when you need or mutability). ChainMap layers dicts so lookups fall through, ideal for defaults + overrides.
from collections import namedtuple, ChainMap
Point = namedtuple("Point", "x y")
p = Point(3, 4)
print(p, p.x, p[1], p._asdict())
defaults = {"theme": "light", "sounds": True}
user = {"theme": "dark"}
settings = ChainMap(user, defaults)
print(settings["theme"], settings["sounds"])itertools composes lazily. chain ; islice any iterator; product, permutations and combinations arrangements; groupby clusters consecutive equal keys; accumulate produces running totals.
from itertools import chain, islice, product, combinations, groupby, accumulate, count
print(list(chain([1, 2], (3, 4), "ab")))
print(list(islice(count(10, 5), 4))) # 10 15 20 25
print(list(product("ab", [1, 2])))
print(list(combinations(["bread", "cheese", "olives"], 2)))
print(list(accumulate([100, 20, -30, 50])))
team = [("design", "Maya"), ("design", "Ada"), ("support", "Sam"), ("design", "Leo")]
for dept, group in groupby(sorted(team), key=lambda m: m[0]):
print(dept, [name for _, name in group])Common question
Do I need the special containers when dict and list already exist?
The collections module gives you containers shaped for common jobs: Counter for tallies, defaultdict for grouping, deque for queues and sliding windows, namedtuple for light records. itertools gives you lazy building blocks for looping: chaining, slicing, grouping, combining. Together they replace many small hand-written loops with one named, tested operation.
Learn the handful you will use every week (Counter, defaultdict, deque, chain, islice, groupby) and reach for them whenever a loop starts with "make an empty thing, then for each item...". The result is shorter, faster and, because the name says what it does, easier to read.
Source: The Zen of Python: "There should be one — and preferably only one — obvious way to do it." For counting, the obvious way is Counter.
Common mix-ups
Each of these sounds reasonable. Think about why it is wrong, then open the explanation.
Anything Counter does can be done with a dict and a loop, so Counter is redundant.
A is as good as a deque for a queue.
itertools build lists, so they use as much as a .
Practice
Write group_by_length(words) that returns a dict mapping each length to the list of words of that length, in original order. Then write top_letters(text, n) that returns the n most common letters (ignoring case and non-letters) as (letter, count) .
Group and count
from collections import defaultdict, Counter
def group_by_length(words):
...
def top_letters(text, n):
...
print(group_by_length(["red", "to", "yellow", "blue", "sky"]))
print(top_letters("Hello, balloon!", 2))
Press Run to try your code, then Check answer to grade it.Write moving_average(values, size) that returns a list of the average of each window of size consecutive values. Use a deque with maxlen. moving_average([1, 2, 3, 4, 5], 3) gives [2.0, 3.0, 4.0].
Sliding window
from collections import deque
def moving_average(values, size):
...
print(moving_average([1, 2, 3, 4, 5], 3))
Press Run to try your code, then Check answer to grade it.Check yourself
What does Counter("banana")["z"] return?
Tip: use the ← and → keys to move between lessons.