Collections
Array
var nums = [5, 2, 8]
nums.append(1) // [5, 2, 8, 1] O(1) on average
nums.insert(9, at: 0) // [9, 5, 2, 8, 1] O(n): everything shifts
let last = nums.removeLast() // 1 O(1), crashes if empty
let maybe = nums.popLast() // Optional(8) O(1), nil if empty
let first = nums.removeFirst() // 9 O(n): everything shifts
nums.swapAt(0, 1) // [2, 5]
print(nums, last, maybe!, first)Ways to create an array:
let zeros = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
let oneToFive = Array(1...5) // [1, 2, 3, 4, 5]
let evens = Array(stride(from: 0, to: 10, by: 2)) // [0, 2, 4, 6, 8]
print(zeros, oneToFive, evens)Searching:
let a = [3, 1, 4, 1, 5]
print(a.contains(4)) // true, O(n)
print(a.firstIndex(of: 1)!) // 1
print(a.lastIndex(of: 1)!) // 3
print(a.firstIndex { $0 > 3 }!) // 2
print(a.min()!, a.max()!) // 1 5
print(a.count(where: { $0 == 1 })) // 2
print(a.allSatisfy { $0 > 0 }) // trueThe slice trap
A slice keeps the indices of the original array. It does not start at 0.
let arr = [10, 20, 30, 40]
let slice = arr[1...2] // [20, 30]
print(slice.startIndex) // 1, not 0
// slice[0] // crash: index out of range
let copy = Array(slice) // [20, 30], starts at 0 again
print(copy[0]) // 202D arrays
let rows = 3
let cols = 4
var grid = Array(repeating: Array(repeating: 0, count: cols), count: rows)
grid[1][2] = 7
for r in 0..<rows {
for c in 0..<cols where grid[r][c] == 7 {
print("found 7 at", r, c) // found 7 at 1 2
}
}Arrays are values in Swift. So the rows above are independent copies. (In Python, [[0] * n] * m
shares one row. Swift does not have this bug.)
Grid neighbors, a pattern you will use in many problems:
func neighbors(_ r: Int, _ c: Int, rows: Int, cols: Int) -> [(Int, Int)] {
let directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] // right, down, left, up
var result: [(Int, Int)] = []
for (dr, dc) in directions {
let nr = r + dr
let nc = c + dc
if nr >= 0 && nr < rows && nc >= 0 && nc < cols {
result.append((nr, nc))
}
}
return result
}
print(neighbors(0, 0, rows: 3, cols: 3)) // [(0, 1), (1, 0)]Dictionary
var counts: [String: Int] = [:]
for word in ["a", "b", "a"] {
counts[word, default: 0] += 1 // no need to check if the key exists
}
print(counts["a"]!) // 2
print(counts["z"] as Any) // nil
counts["b"] = nil // removes "b"
if let value = counts["a"] {
print(value) // 2
}The order of a dictionary is random, and it can change between runs. Sort when order matters:
let scores = ["bo": 3, "al": 5, "cy": 1]
for (name, score) in scores.sorted(by: { $0.key < $1.key }) {
print(name, score) // al 5, bo 3, cy 1
}
let best = scores.max { $0.value < $1.value }!.key
print(best) // alUseful initializers:
let words = ["eat", "tea", "tan", "ate", "nat"]
let groups = Dictionary(grouping: words) { String($0.sorted()) }
// ["aet": ["eat", "tea", "ate"], "ant": ["tan", "nat"]]
let pairs = Dictionary(uniqueKeysWithValues: [("a", 1), ("b", 2)]) // crashes on duplicate keys
let merged = Dictionary([("a", 1), ("a", 5)], uniquingKeysWith: +) // ["a": 6]
let doubled = pairs.mapValues { $0 * 2 } // ["a": 2, "b": 4]
print(groups.count, merged, doubled.count)Set
var seen: Set<Int> = []
let result = seen.insert(3)
print(result.inserted) // true
print(seen.insert(3).inserted) // false: it was already there
print(seen.contains(3)) // true, O(1) on average
seen.remove(3)
let a: Set = [1, 2, 3]
let b: Set = [2, 3, 4]
print(a.union(b).sorted()) // [1, 2, 3, 4]
print(a.intersection(b).sorted()) // [2, 3]
print(a.subtracting(b).sorted()) // [1]
print(a.symmetricDifference(b).sorted()) // [1, 4]
print(a.isSubset(of: b)) // falseinsert returns a tuple. Checking .inserted lets you "add and check" in one step.
Tuples
Tuples group a few values without a new type. They are great for returning two values.
func minMax(_ nums: [Int]) -> (min: Int, max: Int)? {
guard let first = nums.first else { return nil }
return nums.reduce((first, first)) { (min($0.0, $1), max($0.1, $1)) }
}
if let range = minMax([4, 9, 1]) {
print(range.min, range.max) // 1 9
}
let (quotient, remainder) = (17 / 5, 17 % 5) // (3, 2)
print(quotient, remainder)Tuples are not Hashable. You cannot put them in a Set or use them as dictionary keys.
// var visited = Set<(Int, Int)>() // error: (Int, Int) is not Hashable
struct Cell: Hashable {
let r: Int
let c: Int
}
var visited: Set<Cell> = [Cell(r: 0, c: 0)]
visited.insert(Cell(r: 1, c: 2))
// Or turn the pair into one Int: key = r * cols + c
let cols = 10
let key = 2 * cols + 3 // row 2, col 3 -> 23
print(visited.count, key)Tuples can be compared with < (up to 6 items). The first item decides, then the next:
let items = [(2, "b"), (1, "z"), (2, "a")]
let ordered = items.sorted(by: <)
print(ordered) // [(1, "z"), (2, "a"), (2, "b")]Sorting with closures
var scores = [3, 1, 2]
scores.sort() // in place: [1, 2, 3]
let descending = scores.sorted(by: >) // a new array: [3, 2, 1]
print(scores, descending)
struct Person {
let name: String
let age: Int
}
let people = [
Person(name: "Ana", age: 30),
Person(name: "Bo", age: 25),
Person(name: "Cy", age: 30),
]
// Age high to low; for the same age, name A to Z
let sortedPeople = people.sorted {
if $0.age != $1.age { return $0.age > $1.age }
return $0.name < $1.name
}
print(sortedPeople.map(\.name)) // ["Ana", "Cy", "Bo"]
let intervals = [[5, 6], [1, 3], [2, 4]]
print(intervals.sorted { $0[0] < $1[0] }) // [[1, 3], [2, 4], [5, 6]]Sorting is O(n log n). If the order of equal items matters, add a tie-breaker, like the name above.
map, filter, reduce
let nums = [1, 2, 3, 4]
print(nums.map { $0 * $0 }) // [1, 4, 9, 16]
print(nums.filter { $0 % 2 == 0 }) // [2, 4]
print(nums.reduce(0, +)) // 10
print(["1", "x", "3"].compactMap { Int($0) }) // [1, 3]: nil values are dropped
print([[1, 2], [3]].flatMap { $0 }) // [1, 2, 3]Use reduce(into:) to build a collection. It changes one value in place. Plain reduce would copy
the dictionary on every step.
let parity = [1, 2, 3, 4, 5].reduce(into: [String: Int]()) { counts, n in
counts[n % 2 == 0 ? "even" : "odd", default: 0] += 1
}
print(parity["odd"]!, parity["even"]!) // 3 2zip and enumerated
zip walks two sequences together. It stops at the shorter one.
let names = ["a", "b", "c"]
let ages = [1, 2]
for (name, age) in zip(names, ages) {
print(name, age) // a 1, then b 2
}
let values = [1, 3, 6, 10]
let gaps = zip(values, values.dropFirst()).map { $1 - $0 }
print(gaps) // [2, 3, 4]: difference between neighborsenumerated gives you a counter and the value:
for (index, value) in ["x", "y"].enumerated() {
print(index, value) // 0 x, then 1 y
}The counter always starts at 0. On a slice, it is not the real index. Use indices or
zip(slice.indices, slice) there.
Loops and ranges
for i in stride(from: 10, through: 0, by: -3) { print(i, terminator: " ") } // 10 7 4 1
print()
for i in (0..<4).reversed() { print(i, terminator: " ") } // 3 2 1 0
print()
let n = 0
for i in 0..<n { print(i) } // fine: runs zero times
// for i in 0...n - 1 { } // crash when n == 0: the range would be 0...-1Prefer 0..<n over 0...n-1. The closed range crashes when n is 0.
Numbers
print(Int.max) // 9223372036854775807
// Int.max + 1 // crash: Swift traps on overflow
print(Int.max &+ 1 == Int.min) // true: &+ wraps around on purpose
print(-7 / 2) // -3: rounds toward zero
print(-7 % 3) // -1: the sign follows the left side
print(((-7 % 3) + 3) % 3) // 2: a modulo that is never negative
print(abs(-5), min(3, 7), max(3, 7)) // 5 3 7
print(Double(7) / 2) // 3.5Big-O cheat sheet
| Operation | Array | Set | Dictionary |
|---|---|---|---|
| Read by index or key | O(1) | - | O(1) average |
contains | O(n) | O(1) average | O(1) average (keys) |
| Add | O(1) at the end, O(n) at the front | O(1) average | O(1) average |
| Remove | O(1) at the end, O(n) at the front | O(1) average | O(1) average |
| Sort | O(n log n) | - | - |
Rule of thumb: if you call contains or firstIndex inside a loop, you probably want a Set or a
Dictionary instead.
Interview tips
- Say the cost of what you call: "
containson an array is O(n), so I'll use aSet." - Count with
dict[key, default: 0] += 1. It is short and needs no optional unwrapping. - Dictionary and set order is random. Sort before you print or return ordered results.
first,last,min()andmax()return optionals. Think about the empty input before you write!.- For a "visited" set of grid cells, use a small
Hashablestruct orr * cols + c, not a tuple. - If you know the final size, reserve it once:
var squares: [Int] = []
squares.reserveCapacity(1000) // one memory allocation instead of many
for i in 0..<1000 {
squares.append(i * i)
}
print(squares.count) // 1000