013. Valid Anagram
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")
=> TrueOrder changes but multiplicities match.
Example 2
is_anagram("aab", "abb")
=> FalseThe sets match but the counts do not.
Example 3
is_anagram("", "")
=> TrueBoth 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
- Reject unequal lengths.
- Count every character in a.
- For each character in b reject an exhausted supply.
- Decrement its count.
- Accept after all of b is consumed.
Visualization
supply aab: a=2, b=1
consume aba: a -> b -> a
remaining: a=0, b=0Python 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
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.