LambdaLabTM
Computer Science · Class 11 · Dictionary Revisited
DictionariesCounting⏱️ 15 min read

Counting with a Dictionary

“Count how many times each character appears in a string” is the program the syllabus names, and it is the one worth knowing by heart — because it is four lines, and because the same four lines count words, marks, grades, dice rolls and anything else you are ever asked to tally.

1Program 1 — every character in a string

📋 The problem

Count how many times each character appears in the word 'programming'.

char_count.py
# how many of each character?

text = 'programming'
freq = {}

for ch in text:
    if ch in freq:
        freq[ch] = freq[ch] + 1
    else:
        freq[ch] = 1

print(freq)
Output
{'p': 1, 'r': 2, 'o': 1, 'g': 2, 'a': 1, 'm': 2, 'i': 1, 'n': 1}
freq = {}

An empty dictionary, made before the loop. This is the collector — the same idea as total = 0, for an answer that is many numbers rather than one.

for ch in text:

A string is a sequence of characters, so the loop hands over one letter at a time. Nothing about the dictionary changes that.

if ch in freq:

On a dictionary, `in` asks about the KEYS. So this is 'have I made a counter for this letter yet?'

freq[ch] = freq[ch] + 1

Read the count out, add one, put it back. count = count + 1 with the counter picked by name.

freq[ch] = 1

First sighting: make the counter, starting at 1 — not 0, because you are looking at one right now.

Key Takeaway
ch in freq looks at the keys, never the values. This is the same in that searches a tuple, and on a dictionary it always means “is this a key?”. Asking about the values needs ch in freq.values(), which is a different question and a rarer one.

2Program 2 — printing the tally properly

print(freq) shows the raw dictionary, braces and all. A report walks it — and this is the loop the last chapter was building towards:

char_report.py
# walk the tally to print it as a report

text = 'programming'
freq = {}

for ch in text:
    freq[ch] = freq.get(ch, 0) + 1

for ch, n in freq.items():
    print(ch, '->', n)
Output
p -> 1
r -> 2
o -> 1
g -> 2
a -> 1
m -> 2
i -> 1
n -> 1

Two loops, and they do quite different jobs. The first builds the dictionary, walking the string. The second reads it, walking the dictionary. Trying to print inside the first loop prints a running total after every letter, which is a different and much less useful thing.

The building loop has also shrunk to one line, using get() with a fallback of 0 — the shorter form from the previous chapter. It does exactly what the four-line if…else did.

3Program 3 — only the characters that repeat

📋 The problem

Print only the characters that appear more than once, with their counts.

repeats_only.py
# build the tally, then filter it while reading

text = 'programming'
freq = {}

for ch in text:
    freq[ch] = freq.get(ch, 0) + 1

for ch, n in freq.items():
    if n > 1:
        print(ch, 'appears', n, 'times')
Output
r appears 2 times
g appears 2 times
m appears 2 times
Tip
The filter belongs in the second loop, not the first. You cannot know whether a letter repeats until the whole string has been walked — r looks like a one-off right up to the moment the second one arrives. Build the complete tally, then ask questions of it.

4Program 4 — spaces skipped, case folded

On a real sentence, two things spoil the raw count: the spaces get counted, and 'M' and 'm' are tallied as different characters. Both are one line each:

char_count_clean.py
# skip the spaces, treat 'M' and 'm' as the same letter

text = 'Mississippi River'
freq = {}

for ch in text.lower():
    if ch != ' ':
        freq[ch] = freq.get(ch, 0) + 1

print(freq)
Output
{'m': 1, 'i': 5, 's': 4, 'p': 2, 'r': 2, 'v': 1, 'e': 1}

text.lower() in the loop header hands the loop a lowercased copy — the original text is untouched, because a string cannot be changed. The if ch != ' ': is the filter that keeps spaces out of the tally.

5Program 5 — counting a set you already know

Sometimes you know the keys in advance — the five vowels, the four grades. Then the dictionary can be built full of zeros before the loop, and the loop only ever adds:

vowel_count.py
# the keys are known in advance, so make them all at the start

text = 'she sells sea shells on the sea shore'
vowels = {'a': 0, 'e': 0, 'i': 0, 'o': 0, 'u': 0}

for ch in text:
    if ch in vowels:
        vowels[ch] = vowels[ch] + 1

for v, n in vowels.items():
    print(v, '->', n)
Output
a -> 2
e -> 7
i -> 0
o -> 2
u -> 0
Key Takeaway
This version reports the zeros, and that is the point of it. There is no i and no u in the sentence. The grow-as-you-go version would simply not mention them; this one says 0, because the compartment was made before the counting started. When the question asks for a fixed set of categories, that is what you want.

if ch in vowels: is doing double duty here — it is both the filter (ignore consonants and spaces) and the guarantee that no new key can ever appear.

6Program 6 — counting words instead of letters

Nothing about the tally cares what it is counting. split() cuts a sentence into a list of words, and the same loop runs over that instead:

word_count.py
# the same four lines, over words

line = 'the cat sat on the mat and the cat slept'
freq = {}

for w in line.split():
    freq[w] = freq.get(w, 0) + 1

for w, n in freq.items():
    print(w, '->', n)
Output
the -> 3
cat -> 2
sat -> 1
on -> 1
mat -> 1
and -> 1
slept -> 1
word_count_top.py
# which word turns up most often?

line = 'the cat sat on the mat and the cat slept'
freq = {}

for w in line.split():
    freq[w] = freq.get(w, 0) + 1

top_word = ''
top_count = 0

for w, n in freq.items():
    if n > top_count:
        top_count = n
        top_word = w

print('Commonest word:', top_word, '-', top_count, 'times')
Output
Commonest word: the - 3 times

The champion program, over .items(). Two variables move together inside one if, exactly as they did on the tuple of records — the shape does not change just because the data is a dictionary.

word_count_top.py

7Program 7 — the dice tally, in order

The frequency tuple from the previous chapter, now read out properly. A dictionary keeps insertion order, so the raw walk reports faces in the order they were first rolled; sorted() puts them in the order a reader expects:

dice_tally.py
# insertion order — the order each face was first seen

rolls = (3, 6, 2, 6, 4, 6, 1, 2)
freq = {}

for r in rolls:
    freq[r] = freq.get(r, 0) + 1

for face, n in freq.items():
    print('Face', face, 'came up', n, 'time(s)')
Output
Face 3 came up 1 time(s)
Face 6 came up 3 time(s)
Face 2 came up 2 time(s)
Face 4 came up 1 time(s)
Face 1 came up 1 time(s)
dice_tally_sorted.py
# the same tally, walked in order of face

rolls = (3, 6, 2, 6, 4, 6, 1, 2)
freq = {}

for r in rolls:
    freq[r] = freq.get(r, 0) + 1

for face in sorted(freq):
    print('Face', face, 'came up', freq[face], 'time(s)')
Output
Face 1 came up 1 time(s)
Face 2 came up 2 time(s)
Face 3 came up 1 time(s)
Face 4 came up 1 time(s)
Face 6 came up 3 time(s)

sorted(freq) hands back the keys in order, so the value comes from freq[face] rather than from a second loop variable. Face 5 is missing from both, because it was never rolled — the grow-as-you-go tally only ever holds what it has seen.

8Recap

One loop builds, another loop reads

Walk the data to fill the dictionary; walk the dictionary to report it. Printing inside the first loop shows a running total instead.

freq[x] = freq.get(x, 0) + 1

The whole tally in one line. The 0 is what makes a first sighting work — without it, get() hands back None and None + 1 crashes.

`in` on a dictionary means 'is this a key?'

Never a value. That is what makes the if…else version of the tally work.

Known categories? Start them at zero

vowels = {'a': 0, …} reports the ones that never appeared. The grow-as-you-go version simply omits them.

✍️ Now write these yourself
  1. 1

    Count the characters of a word typed in with input().

    Hint · Program 1 with text = input('Word: ').

  2. 2

    Count how many words of each length a sentence has.

    Hint · The key is len(w), not w — a number. Keys do not have to be strings.

  3. 3

    Count the vowels and the consonants separately.

    Hint · Two counters would do it, but a dictionary with two keys — {'vowel': 0, 'consonant': 0} — prints as a report.

  4. 4

    From a tuple of grades, print how many students got each grade, in alphabetical order of grade.

    Hint · Tally with get(), then walk sorted(tally).

  5. 5

    Print the first character that appears exactly twice.

    Hint · Build the tally, then walk it with a break on the first n == 2.

Quick Check

Why must the report loop come after the counting loop, not inside it?

Quick Check

In freq[ch] = freq.get(ch, 0) + 1, what is the 0 for?

Quick Check

Why does the vowel program report 'i -> 0' but the character tally never mentions letters that are absent?