Skip to content

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. Use if let, ??, or default:.
  • Relying on iteration order. It is deliberately unpredictable.
  • Using a type that isn't Hashable as a key — conform it, or the compiler will tell you.
  • Mutating a dictionary declared with let.
  • Using map on a dictionary expecting a dictionary. map returns an array; use mapValues / filter to stay a dictionary, or Dictionary(uniqueKeysWithValues:) to rebuild.
  • Dictionary(uniqueKeysWithValues:) with duplicate keys crashes. Use Dictionary(_:uniquingKeysWith:).

Exercises

  1. Count how often each word appears in a sentence and print the words sorted by frequency, then alphabetically.
  2. Given two arrays of student names (math class and art class), print who is in both, only math, and either.
  3. Check whether two strings are anagrams (hint: compare character counts).
  4. Group ["eat", "tea", "tan", "ate", "nat", "bat"] into anagram groups.
  5. Write a phone book: a loop that reads add name number, find name, quit commands.
  6. Use Dictionary(grouping:by:) to bucket 1...20 into "even"/"odd".

Recap

  • [Key: Value] — lookups return optionals; use default: and ??.
  • dict[key, default: 0] += 1 is the counting idiom.
  • Iteration order is unspecified; sort when it matters.
  • Set gives uniqueness and fast membership, plus union/intersection/etc.
  • Keys and set elements must be Hashable.

Next: Functions →