Counting with a dictionary
A common task is to count how many times each item occurs: a word in a text or a name in a call log. The phone book helps with such counting again.
Picture a call log: Anna, Boris, Anna again. To find out who called how many times, keep a book where each name has not a number next to it but a count of calls. Meet a name in the log, find its line and add one. If the name isn't in the book yet, write it in with the count 1.
In Swift such a book is a [String: Int] dictionary: what you count serves as the key, and the counter as the value. Here the book picture is inaccurate. On paper you count a name that isn't in the book as zero calls. But in Swift a lookup of a missing key gives nil, and you can't add one to it.
Lookup with a fallback value
For this case a dictionary has a lookup with a fallback value. The form counts[name, default: 0] reads as "the value for the key name, or 0 if there is no such key". Here default: is an argument label, not a switch branch. The result of such a lookup is a plain Int, with no question mark:
let counts = ["Anna": 2, "Boris": 1]
print(counts["Anna", default: 0]) // 2
print(counts["Gleb", default: 0]) // 0Let's go through the program line by line:
- The first line creates a dictionary of call counters: Anna called twice, Boris once.
- The key
"Anna"is there, and the lookup gives the stored value2. - There is no key
"Gleb", and the lookup gives the fallback value0.
For reading, this form works like counts["Gleb"] ?? 0. The difference shows on a write: through default: you can store a new value in the dictionary, and through ?? you can't.
Counting in a loop
The statement callCount[name, default: 0] += 1 takes three steps:
- It finds the value for the key, or takes
0if there is no such key. - It adds one to it.
- It stores the result under the same key. If the key was missing, a new pair appears in the dictionary.
Press "Next step" and watch the dictionary callCount after each run of line 4. The widget writes the pairs in the order they appeared, while a real dictionary keeps no order.
1let calls = ["Anna", "Boris", "Anna"]2var callCount: [String: Int] = [:]3for name in calls {4 callCount[name, default: 0] += 15}6print(callCount["Anna", default: 0])7print(callCount["Gleb", default: 0])8print(callCount.count)
The log holds three calls: Anna called twice, Boris once.
Look at the last steps: a read with default: does not add the key. After callCount["Gleb", default: 0] the dictionary still has two pairs. A pair appears only when something is written through default:, as in += 1.
"Count how many times each word occurs" is one of the most common interview tasks. In Swift it is solved with a [String: Int] dictionary and a loop with counts[word, default: 0] += 1.
Common mistake
The counter is increased without default::
var callCount: [String: Int] = [:]
callCount["Anna"] += 1 // ✗error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'The program does not compile: callCount["Anna"] has the type Int?, and += can add a number to an Int but not to an Int?. There is no key "Anna" yet, so there is nothing to add one to. A fallback value solves it: callCount["Anna", default: 0] += 1.
Key points
dict[key, default: 0]gives a plain value, and+= 1through it stores the pair if it was missing.- A read with
default:does not change the dictionary: a pair appears only on a write. - Without
default:a lookup gives anInt?, and+= 1on it does not compile.