Arrays, dictionaries and sets
The three collections you will use daily, and the transform methods that replace most hand-written loops.
By the end you will be able to
- Choose between Array, Dictionary and Set for a given job
- Transform collections with map, filter, reduce, compactMap and sorted
- Explain why collections are value types and what that means when you assign one
What does this print?
var a = [1, 2, 3]
var b = a
b.append(4)
print(a.count)Three collections, three jobs
| Type | Holds | Ordered? | Duplicates? | Lookup cost |
|---|---|---|---|---|
Array | anything | yes | yes | by index: instant; by value: scans |
Dictionary | key → value | no | keys unique | by key: instant |
Set | anything hashable | no | no | membership: instant |
Pick by the question you need to answer fast. "What is the third one?" → array. "What is the value for this key?" → dictionary. "Have I seen this before?" → set.
Arrays
var names = ["Ada", "Alan"]
names.append("Grace")
names.insert("Edsger", at: 0)
names.remove(at: 1)
names.count // how many
names.isEmpty // better than count == 0
names.first // Optional — the array might be empty
names.last // Optional
names[0] // NOT optional — traps if out of range
first and last are optional; subscripting is not. That is deliberate: an empty array is an ordinary situation, whereas an index you computed wrongly is a bug you want to hear about immediately. Picture the refused alternative: if numbers[10] returned an optional, every ordinary access would grow ?? ceremony — and a genuinely wrong index would produce a nil that flows onward and surfaces as a blank label three screens away. The trap converts "quietly wrong somewhere downstream" into "loudly wrong on this exact line".
Dictionaries
var ages = ["Ada": 36, "Alan": 41]
ages["Grace"] = 45 // insert or replace
ages["Alan"] = nil // remove
ages["Ada"] // Int? — the key might be absent
ages["Nobody", default: 0] // Int — supply a fallback inline
The default: subscript is the neat solution to counting:
Sets
var seen: Set<String> = ["a", "b"]
seen.insert("c")
seen.contains("b") // instant
seen.union(other)
seen.intersection(other)
seen.subtracting(other)
Note the type annotation. ["a", "b"] on its own is an array literal — you have to say Set<String> for Swift to build a set.
Transforms replace loops
This is the part that changes how your code reads.
let numbers = [1, 2, 3, 4, 5]
numbers.map { $0 * 2 } // [2, 4, 6, 8, 10] transform each
numbers.filter { $0 % 2 == 0 } // [2, 4] keep some
numbers.reduce(0, +) // 15 combine into one
numbers.contains { $0 > 4 } // true
numbers.allSatisfy { $0 > 0 } // true
numbers.first { $0 > 3 } // Optional(4)
numbers.sorted { $0 > $1 } // [5, 4, 3, 2, 1]
$0 is the first argument to the closure — shorthand you will see constantly. The next lesson covers closures properly; for now, read { $0 * 2 } as "given an element, produce twice it".
These compose, and each step names what it does:
compactMap — transform and drop the nils
["1", "two", "3"].compactMap { Int($0) } // [1, 3]
map would give [Optional(1), nil, Optional(3)]. compactMap unwraps and discards, which is almost always what you wanted.
reduce — collapse to a single value
prices.reduce(0, +) // sum
names.reduce("") { $0 + $1 } // concatenate
numbers.reduce(0) { max($0, $1) } // largest
The first argument is the starting value; the closure receives the running result and the next element.
map and filter each build a new array. What does this print?
let source = [1, 2, 3, 4, 5, 6]
let result = source
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.reduce(0, +)
print(result)
print(source)Value semantics, one more time
Assigning a collection copies it. Passing one into a function copies it. This is the opposite of Java, Python, and JavaScript, and it removes a permanent source of "who else is holding a reference to this array?" bugs. The reference-world incident goes like this: a screen hands its items to a helper, the helper sorts its list to build a summary — and the screen's on-display order silently changes too, because there was only ever one list. In Swift that program is unwritable: the helper sorted its own copy.
The obvious worry is cost, and Swift handles it: collections are copy-on-write. The copy is deferred until one side actually mutates, so var b = a is cheap and only becomes a real copy if you write to it. You get value semantics at reference-assignment prices.
You need to check membership repeatedly against 50,000 stored ids while looping over 10,000 incoming ones. Which do you reach for?
Word frequencies
Count how often each word appears and print the results sorted by word.
Chain the transforms
From the orders below, print the total value of orders over 20 that were placed by members, as one number.
Deduplicate, keeping order
Remove duplicates from an array while preserving first-seen order. A plain Set would lose the order.
Copy-on-write lets Swift give you value semantics without copying on every assignment. Explain in your own words what a program would have to do differently if collections were reference types instead — and name one bug that would become possible.
You can now:
- Choose between
Array,DictionaryandSetfrom the question you need answered - Transform with
map,filter,reduce,compactMapandsorted - Explain value semantics and copy-on-write, and predict what assignment does
Next up: functions — argument labels, defaults, and why Swift function names read like sentences.