Swift foundations · intro
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?
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.