Course outline · 0% complete

0/27 lessons0%

Course overview →

collections: Counter, defaultdict, deque

lesson 6-1 · ~14 min · 15/27

From lesson 2-3, sorted(d.items(), key=lambda kv: kv[1]) sorts a dict's pairs by value. Each kv is a (key, value) pair, so kv[1] reaches the value half.

You are about to meet Counter, which bakes that exact sort-by-count pattern into a single method named most_common, so you rarely need to write the lambda yourself.

Counter: counting without boilerplate

The standard library exists so you do not rewrite, and re-debug, the same chores in every project. Counting things and grouping things are the two most common data chores in working Python, and this unit's collections module turns each into a line or two.

Counting things in Python for Beginners took four lines: check if the key exists, initialize it, increment. collections.Counter does it in one:

from collections import Counter

votes = ["cat", "dog", "cat", "bird", "cat"]
tally = Counter(votes)
# Counter({'cat': 3, 'dog': 1, 'bird': 1})

A Counter is a dict (it subclasses dict, inheritance from lesson 3-4 in the wild), with extras:

  • tally.most_common(2) gives the top two (item, count) pairs.
  • Missing keys return 0 instead of raising KeyError.
  • Feed it any iterable: a list, a string, a generator from unit 4.

Counting letters in a word

Counter takes any iterable and tallies it. One constructor call does the counting, and one method call ranks the results.

from collections import Counter

text = "mississippi"
letters = Counter(text)

print(letters["s"])
print(letters["z"])
print(letters.most_common(2))

Output

4
0
[('i', 4), ('s', 4)]

The middle line is the detail people miss. Looking up a letter that never appeared returns 0 instead of raising KeyError, because a Counter treats absent items as having a count of zero. That removes the if key in counts guard you would need with a plain dict.

Finding the winning vote

A Counter tallies the ballots, and most_common(1) picks out the top entry. The method always returns a list of (item, count) pairs, even when you ask for just one, so the code indexes into it and unpacks.

from collections import Counter

votes = ["red", "blue", "red", "green", "red", "blue"]

tally = Counter(votes)
winner, count = tally.most_common(1)[0]
print(winner, count)

Output

red 3

tally.most_common(1) evaluates to [('red', 3)], a one-item list. Indexing with [0] gets the pair out, and winner, count = ... unpacks it into two names in one step. Forgetting the [0] is the classic slip here, and it leaves you holding a list where you expected a tuple.

defaultdict and deque

defaultdict is a dict that invents a default value for missing keys. Pass it the factory to use, like list or int:

from collections import defaultdict

groups = defaultdict(list)
groups["fruit"].append("apple")   # no KeyError, a fresh [] appears

That kills the if key not in d: d[key] = [] dance forever, and it is the standard tool for grouping.

deque (say "deck", short for double-ended queue) stores its items so that both ends are directly reachable. As a result, appendleft and popleft take the same tiny, fixed amount of time no matter how many items the deque holds. A plain list cannot offer that: list.pop(0) must shift every remaining element one slot left, so its cost grows with the length of the list. Use a deque whenever you need a queue, like breadth-first search in the Data Structures course. (Unit 9 turns this kind of cost reasoning into a formal habit.)

Grouping words by first letter

defaultdict(list) creates an empty list automatically the first time any key is touched, which removes the "is this key here yet" check that grouping code otherwise needs.

from collections import defaultdict

words = ["apple", "banana", "avocado", "cherry", "blueberry"]

groups = defaultdict(list)
for word in words:
    groups[word[0]].append(word)

print(dict(groups))

Output

{'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

word[0] is the first letter, so groups[word[0]].append(word) is the entire loop body. With a plain dict that single line would need a preceding if word[0] not in groups: groups[word[0]] = []. The argument to defaultdict is the factory to call, so it is list, not list(). Wrapping the result in dict(groups) before printing just gives the plain dict display rather than the longer defaultdict(...) form.

Removing from the front of a queue

For a queue where items leave from the front, deque beats list. list.pop(0) has to shift every remaining element one slot to the left, while deque.popleft() unhooks the item directly.

Operationlistdeque
append at the endO(1)O(1)
remove from the frontO(n)O(1)

A list stores its items in one contiguous block, so index 0 disappearing means everything behind it slides down, and that work grows with the length of the list. A deque (short for double-ended queue) is built so both ends are directly reachable, making popleft a fixed-cost operation no matter how long the queue gets. Unit 9 turns this kind of cost reasoning into a habit.

list.pop(0)every remaining item slides one slot left, O(n)abcd3 moves for 4 itemsdeque.popleft()both ends are directly reachable, O(1)abcd1 unhook, nothing movesthe rest keep their places
Popping the front of a list shifts every remaining element, while a deque unhooks the end item directly and leaves the others in place.