What a hash map is
A hash map stores key-value pairs. You give it a key and it hands back the value attached to that key, without scanning through everything it holds. Lookups, insertions and removals take O(1) time on average. Use one whenever you need to answer questions such as "how many times has this word appeared?" or "have I seen this number before?".
In Swift, the hash map is the Dictionary type. A dictionary holding strings as keys and integers as values has the type [String: Int]. The keys must be hashable (strings, integers and characters all are), and each key appears at most once.
Creating a dictionary
An empty dictionary needs its types spelled out, because Swift can't guess them:
var digitValue = [String: Int]()
var other: [String: Int] = [:]
With initial contents, write a literal. Swift infers the type from it, but you may state the type too:
var digitValue: [String: Int] = [
"zero": 0,
"one": 1
]
Use var for a dictionary you will change. A dictionary declared with let can't be modified.
Reading values
Subscripting with a key returns an optional, because the key might be missing:
digitValue["zero"] // Optional(0)
digitValue["three"] // nil
You can't use that result as an Int directly. There are three common ways to deal with it:
digitValue["three"] ?? 0gives 0 when the key is missing, and the stored value otherwise.digitValue["three", default: 0]does the same, with the default inside the subscript.if let value = digitValue["one"] { ... }runs the block only when the key exists.
Reading with a default doesn't add the key. After digitValue["three", default: 0], the dictionary still has two entries. The default form also works for updating, so counting is short:
var counts = [String: Int]()
for w in ["a", "b", "a"] {
counts[w, default: 0] += 1
}
// counts["a"] is 2 and counts["b"] is 1
Adding, updating and removing
Assigning through the subscript adds the key if it is new and replaces the value if it exists:
digitValue["three"] = 3 // adds
digitValue["zero"] = 5 // replaces
To remove an entry, call removeValue(forKey:). It returns the removed value as an optional, nil if the key wasn't there, and removing a missing key changes nothing. Assigning nil through the subscript, as in digitValue["one"] = nil, removes the entry too.
Checking and iterating
To test whether a key exists, use digitValue.keys.contains("zero"), or compare the subscript with nil. Both are O(1).
To visit every entry, loop over keys, or over the pairs themselves:
for key in digitValue.keys {
print("\(key) is \(digitValue[key]!)")
}
for (key, value) in digitValue {
print(key, value)
}
Iteration takes O(n) time. A Dictionary has no guaranteed order, and the order may differ from one run of the program to the next. If you need a stable order, sort the keys first: for key in digitValue.keys.sorted().
The exercise
The lesson asks you to manage a stock of fruit, one step at a time:
- Declare
fruitsas a[String: Int]with"apples": 3,"bananas": 5and"grapes": 2. - Add 4 apples. The current count is optional, so
?? 0supplies a number to add to. - Print
Out of pearswhen the"pears"key is missing, by testing the subscript againstnil. - Remove
"bananas"withremoveValue(forKey:). - Loop over
fruits.keysand print each count. The key comes from the dictionary, sofruits[fruit]!can't fail.
var fruits: [String: Int] = [
"apples": 3,
"bananas": 5,
"grapes": 2
]
fruits["apples"] = (fruits["apples"] ?? 0) + 4
if fruits["pears"] == nil {
print("Out of pears")
}
fruits.removeValue(forKey: "bananas")
for fruit in fruits.keys {
print("We have \(fruits[fruit]!) \(fruit).")
}
The program prints Out of pears, then We have 7 apples. and We have 2 grapes.. The two We have lines can come out in either order, because the dictionary has no fixed order. The lesson accepts both.
Gotchas
- Forgetting the optional.
fruits["apples"] + 4doesn't compile, because the left side is anInt?. Unwrap it with??orif let. - Force-unwrapping a missing key.
fruits["pears"]!crashes the program. Use!only when you know the key is present, as in the loop above. - Relying on order. Don't expect entries to come back in the order you wrote them.
- Using
let. A constant dictionary can't be changed. - Copies. Dictionaries are value types. Assigning one to another variable makes an independent copy.
Key takeaways
- A Swift hash map is a
Dictionary, written[Key: Value]. - A subscript read returns an optional. Use
??,default:orif let. - Assign through the subscript to add or update, and use
removeValue(forKey:)to delete. - Lookup, insert and remove are
O(1)on average, and iteration isO(n)in no particular order.
