8. Dictionaries & sets¶
What & why¶
A dictionary maps unique keys to values — look up a price by product name, a user by ID. A set stores unique values with fast membership tests. Both are unordered, both are value types, and both require their keys/elements to be Hashable.
Dictionaries¶
var stock = ["apple": 5, "pear": 2] // [String: Int]
var empty: [String: Int] = [:]
let fromPairs = Dictionary(uniqueKeysWithValues: [("a", 1), ("b", 2)])
Looking up values¶
A lookup returns an optional, because the key might not exist:
let count = stock["apple"] // Optional(5)
let none = stock["kiwi"] // nil
if let n = stock["apple"] {
print("We have \(n) apples")
}
print(stock["kiwi", default: 0]) // 0 — no optional, supplies a fallback
print(stock["kiwi"] ?? 0) // same, using the nil-coalescing operator
Adding, updating, removing¶
stock["banana"] = 10 // add
stock["apple"] = 7 // update
stock["pear"] = nil // remove
let old = stock.updateValue(3, forKey: "apple") // returns the old value (Optional(7))
stock.removeValue(forKey: "banana")
stock["cherry", default: 0] += 1 // increment, creating if missing
That last form is the idiomatic way to count things:
var freq: [Character: Int] = [:]
for ch in "mississippi" {
freq[ch, default: 0] += 1
}
print(freq) // e.g. ["s": 4, "i": 4, "p": 2, "m": 1] — order varies
Iterating¶
Dictionary order is not guaranteed (and varies between runs). Sort if you need stable output:
for (key, value) in stock.sorted(by: { $0.key < $1.key }) {
print("\(key): \(value)")
}
print(stock.keys.sorted())
print(stock.values.reduce(0, +))
print(stock.count, stock.isEmpty)
Transforming¶
let doubled = stock.mapValues { $0 * 2 } // keep keys, change values
let lowStock = stock.filter { $0.value < 5 } // still a [String: Int]
let names = stock.map { "\($0.key)=\($0.value)" } // map returns an ARRAY
// Merge two dictionaries; the closure decides conflicts
let merged = stock.merging(["apple": 100, "fig": 1]) { current, new in current + new }
// Group by a key
let words = ["apple", "avocado", "banana", "blueberry", "cherry"]
let byLetter = Dictionary(grouping: words, by: { $0.first! })
// ["a": ["apple", "avocado"], "b": ["banana", "blueberry"], "c": ["cherry"]]
Sets¶
A Set has no duplicates and no order. Membership checks are fast (O(1) on average).
var tags: Set<String> = ["swift", "ios"]
tags.insert("swift") // no effect — already present
tags.insert("macos")
print(tags.contains("ios")) // true
tags.remove("ios")
print(tags.count) // 2
let unique = Set([3, 1, 3, 2, 1]) // {1, 2, 3} in some order
Set algebra¶
let a: Set = [1, 2, 3, 4]
let b: Set = [3, 4, 5]
print(a.union(b)) // {1, 2, 3, 4, 5}
print(a.intersection(b)) // {3, 4}
print(a.subtracting(b)) // {1, 2}
print(a.symmetricDifference(b)) // {1, 2, 5}
print(a.isSubset(of: [1, 2, 3, 4, 5])) // true
print(a.isDisjoint(with: [9])) // true
Removing duplicates, keeping order¶
A common recipe — a set remembers what you've seen, an array keeps the order:
var seen = Set<Int>()
let deduped = [3, 1, 3, 2, 1].filter { seen.insert($0).inserted }
print(deduped) // [3, 1, 2]
insert returns (inserted: Bool, memberAfterInsert:); .inserted is true only the first time.
What can be a key? Hashable¶
String, Int, Double, Bool, Character, and tuples-of-nothing... all conform to Hashable. Your own types can too:
struct Point: Hashable {
var x: Int
var y: Int
}
var visited: Set<Point> = [Point(x: 0, y: 0)]
visited.insert(Point(x: 1, y: 2))
Swift generates == and hash(into:) automatically when all stored properties are hashable. (Structs get a proper introduction in the next level.)
Choosing a collection¶
| Need | Use |
|---|---|
| Ordered items, index access | Array |
| Look up by key | Dictionary |
| Fast "have I seen this?" / no duplicates | Set |
| Ordered and unique | Array + a Set on the side (or the swift-collections package's OrderedSet) |
Common mistakes¶
- Forgetting that lookups are optional and force-unwrapping with
!— it crashes when the key is missing. Useif let,??, ordefault:. - Relying on iteration order. It is deliberately unpredictable.
- Using a type that isn't
Hashableas a key — conform it, or the compiler will tell you. - Mutating a dictionary declared with
let. - Using
mapon a dictionary expecting a dictionary.mapreturns an array; usemapValues/filterto stay a dictionary, orDictionary(uniqueKeysWithValues:)to rebuild. Dictionary(uniqueKeysWithValues:)with duplicate keys crashes. UseDictionary(_:uniquingKeysWith:).
Exercises¶
- Count how often each word appears in a sentence and print the words sorted by frequency, then alphabetically.
- Given two arrays of student names (math class and art class), print who is in both, only math, and either.
- Check whether two strings are anagrams (hint: compare character counts).
- Group
["eat", "tea", "tan", "ate", "nat", "bat"]into anagram groups. - Write a phone book: a loop that reads
add name number,find name,quitcommands. - Use
Dictionary(grouping:by:)to bucket1...20into "even"/"odd".
Recap¶
[Key: Value]— lookups return optionals; usedefault:and??.dict[key, default: 0] += 1is the counting idiom.- Iteration order is unspecified; sort when it matters.
Setgives uniqueness and fast membership, plus union/intersection/etc.- Keys and set elements must be
Hashable.
Next: Functions →