The whole difficulty is the very first time a value appears — there is nothing to add one to yet. Here are the two ways round it.
# The version everybody writes first
counts = {}
for word in words:
if word in counts:
counts[word] = counts[word] + 1
else:
counts[word] = 1
# The same thing, with the branch folded into the lookup
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
- get(key, 0) makes the first count stop being special
Reading a missing key as 0 means there is no 'first time' case left — every value is just the old count plus one. Removing a branch by choosing a better default is a pattern you will use far beyond counting.
- get() reads, it does not write
counts.get(word, 0) returns 0 without adding the key, unlike setdefault which inserts. Here that distinction does not matter because the very next thing you do is assign. When you are only reading, it matters a lot — setdefault on a lookup quietly grows the dictionary.
- The keys arrive in first-seen order
Assigning a new key appends it; assigning an existing one updates in place without moving it. So the result is ordered by first appearance, which is usually the most useful order and is worth knowing you get for free rather than assuming you must sort.
Build a dict as you go. For each record, work out which value it belongs to and add one to that value’s running total — the only awkward moment is the very first record of each kind.
counts.get(value, 0) gives you 0 when the value has not been seen yet, so the update is the same expression every time: counts[value] = counts.get(value, 0) + 1.
counts = {}. For each record: read record[key] — which raises KeyError for a broken record all by itself, which is what the contract asks for — then set counts[value] to counts.get(value, 0) + 1. Return counts. Three lines, no if.
def count_by(records, key): counts = {} for record in records: value = record[____] counts[value] = counts.____(value, ____) + 1 return counts
def count_by(records, key):
counts = {}
for record in records:
value = record[key]
counts[value] = counts.get(value, 0) + 1
return counts
collections.Counter(record[key] for record in records) produces the same tallies, in the same order, and is what you should reach for in real code. It also gives you .most_common(3) for free, which is usually the next thing anyone asks for. Write it by hand once so the shortcut is a shortcut rather than an incantation.
A Counter is a dict subclass, so it compares equal to the plain dict here and every case would pass. But it keeps its own behaviour after you return it: counter['nonexistent'] gives 0 rather than raising. That is convenient inside a function and surprising across an API boundary — the same argument as defaultdict in the grouping exercise. If it escapes, consider dict(counts).
No explicit check for the missing field, because subscripting already raises KeyError naming the key. This is the third exercise where the right amount of error handling turned out to be none — worth noticing as a pattern rather than a coincidence.
Every version below is correct and verified against the same cases by the build. They are here because knowing which one to reach for is the part that takes judgement, and the trade-off is stated rather than left for you to guess.
from collections import Counter
def count_by(records, key):
return Counter(record[key] for record in records)What you should write in real code — same order, same counts, plus .most_common() free. One caution: a Counter returned across an API boundary still answers 0 for keys nobody added, which surprises callers. Wrap it in dict() if it escapes.
def count_by(records, key):
counts = {}
for record in records:
counts.setdefault(record[key], 0)
counts[record[key]] += 1
return countsWorks, and does two dictionary operations where one would do. setdefault earns its place when the default is a container you then mutate — as in grouping — not when you are about to overwrite the value anyway.
Do not count in Python if the data is in a database — SELECT status, COUNT(*) ... GROUP BY status does it without moving a million rows across the network. And if you need the records themselves and not only the totals, group them instead and take the lengths; counting twice over the same data because you discovered you needed both is a common and avoidable waste.
- Return the top n values by count, and decide how ties should be broken so the output is stable between runs.
- Count by something derived rather than stored — the domain of an email address, the hour of a timestamp — and notice the function barely changes.
- Add a second key so you can count by pairs, and work out whether the result should be nested dicts or a dict keyed by tuples.