Reasoning Into CodeArunank Divya Sharan · Python edition← Arunank’s portfolio
Module 02 · Hash Maps, Sets, and Frequency State

013. Valid Anagram

Foundation · Download this solution · All solutions and shared helpers

Problem

Return whether strings a and b contain exactly the same character multiplicities. Case, spaces and punctuation are significant. The empty string is an anagram of itself. Characters are Python string elements; this kata does not normalize Unicode spellings or grapheme clusters.

Examples

Example 1

is_anagram("anagram", "nagaram")
=> True

Order changes but multiplicities match.

Example 2

is_anagram("aab", "abb")
=> False

The sets match but the counts do not.

Example 3

is_anagram("", "")
=> True

Both multisets are empty.

Reveal the worked solution and reasoning

Why this mechanism works

A set loses multiplicity: aab and abb have the same set but are not anagrams. Build a supply count from a, then consume it with b. A missing supply rejects immediately. Equal lengths ensure that successful consumption cannot leave unmatched characters.

State and boundaries

counts stores unused occurrences supplied by a. Each accepted character of b consumes one occurrence.

Protocol

  1. Reject unequal lengths.
  2. Count every character in a.
  3. For each character in b reject an exhausted supply.
  4. Decrement its count.
  5. Accept after all of b is consumed.

Visualization

supply aab: a=2, b=1
consume aba: a -> b -> a
remaining: a=0, b=0

Python implementation

def is_anagram(a, b):
    if len(a) != len(b):
        return False
    counts = {}
    for char in a:
        counts[char] = counts.get(char, 0) + 1
    for char in b:
        if counts.get(char, 0) == 0:
            return False
        counts[char] -= 1
    return True

Worked execution

Recorded state changes after statements; L numbers refer to the solution file. Long traces are explicitly condensed.

is_anagram("anagram", "nagaram")

L2 is_anagram: if len(a) != len(b):
a='anagram'; b='nagaram'

L4 is_anagram: counts = {}
a='anagram'; b='nagaram'; counts={}

L5 is_anagram: for char in a:
a='anagram'; b='nagaram'; counts={}; char='a'

L6 is_anagram: counts[char] = counts.get(char, 0) + 1
a='anagram'; b='nagaram'; counts={'a':1}; char='a'

L5 is_anagram: for char in a:
a='anagram'; b='nagaram'; counts={'a':1}; char='n'

L6 is_anagram: counts[char] = counts.get(char, 0) + 1
a='anagram'; b='nagaram'; counts={'a':1, 'n':1}; char='n'

L5 is_anagram: for char in a:
a='anagram'; b='nagaram'; counts={'a':1, 'n':1}; char='a'

L6 is_anagram: counts[char] = counts.get(char, 0) + 1
a='anagram'; b='nagaram'; counts={'a':2, 'n':1}; char='a'

... 18 intermediate state changes omitted ...

L7 is_anagram: for char in b:
a='anagram'; b='nagaram'; counts={'a':1, 'n':0, 'g':0, 'r':0, 'm':1}; char='a'

L10 is_anagram: counts[char] -= 1
a='anagram'; b='nagaram'; counts={'a':0, 'n':0, 'g':0, 'r':0, 'm':1}; char='a'

L7 is_anagram: for char in b:
a='anagram'; b='nagaram'; counts={'a':0, 'n':0, 'g':0, 'r':0, 'm':1}; char='m'

L10 is_anagram: counts[char] -= 1
a='anagram'; b='nagaram'; counts={'a':0, 'n':0, 'g':0, 'r':0, 'm':0}; char='m'

Result: True. Order changes but multiplicities match.

is_anagram("aab", "abb")

L2 is_anagram: if len(a) != len(b):
a='aab'; b='abb'

L4 is_anagram: counts = {}
a='aab'; b='abb'; counts={}

L5 is_anagram: for char in a:
a='aab'; b='abb'; counts={}; char='a'

L6 is_anagram: counts[char] = counts.get(char, 0) + 1
a='aab'; b='abb'; counts={'a':1}; char='a'

L6 is_anagram: counts[char] = counts.get(char, 0) + 1
a='aab'; b='abb'; counts={'a':2}; char='a'

L5 is_anagram: for char in a:
a='aab'; b='abb'; counts={'a':2}; char='b'

L6 is_anagram: counts[char] = counts.get(char, 0) + 1
a='aab'; b='abb'; counts={'a':2, 'b':1}; char='b'

L7 is_anagram: for char in b:
a='aab'; b='abb'; counts={'a':2, 'b':1}; char='a'

L10 is_anagram: counts[char] -= 1
a='aab'; b='abb'; counts={'a':1, 'b':1}; char='a'

L7 is_anagram: for char in b:
a='aab'; b='abb'; counts={'a':1, 'b':1}; char='b'

L10 is_anagram: counts[char] -= 1
a='aab'; b='abb'; counts={'a':1, 'b':0}; char='b'

Result: False. The sets match but the counts do not.

Why the solution is correct

Counts begin as the exact multiset of a. Every decrement matches one distinct occurrence. Failure proves a multiplicity mismatch; success after equal-length consumption matches every supplied occurrence exactly once.

Time and space

O(n+m) expected time and O(u) space for u distinct characters.

Common mistakes

Comparing sets ignores repeated letters. Accidentally lowercasing changes the stated contract.

Reasoning card

State: counts stores unused occurrences supplied by a. Each accepted character of b consumes one occurrence.

Invariant: Net counts are zero exactly when multiplicities match.

Decision to rehearse: Build and cancel frequencies without losing zero-count logic.

Finish: Accept after all of b is consumed

Transfer exercise

Return the minimum deletions across both strings needed to make their character multisets equal.