Skip to content
Lesson 12 of 15

Step 1 of 5 · Reading · ~4 min

Learn

Collections

Higher-Order Methods

A higher-order method is one that takes a function as an argument. Swift's collections are built almost entirely out of them, and three do most of the work: map transforms, filter selects, reduce collapses.

let nums = [1, 2, 3, 4, 5]

print(nums.map { $0 * $0 })
print(nums.filter { $0 > 2 })
print(nums.reduce(0, +))

//> [1, 4, 9, 16, 25]
//> [3, 4, 5]
//> 15
MethodInputOutputShape
map[A][B]same count, new values
filter[A][A]same values, fewer of them
reduce[A]Bone value out of many

Why reduce needs a starting value

reduce walks the collection, carrying an accumulator. You must supply what that accumulator starts as, because the collection might be empty and Swift will not guess.

let nums = [1, 2, 3, 4, 5]
print(nums.reduce(0, +))
print(nums.reduce(0) { acc, n in acc + n })
print(nums.reduce(1, *))
print([Int]().reduce(0, +))

//> 15
//> 15
//> 120
//> 0

The first two lines are the same operation written two ways: + is itself a function of type (Int, Int) -> Int, so you can hand it over directly instead of writing a closure. The last line is the payoff — reducing an empty array yields the seed rather than crashing.

Chaining

Each of these returns a new collection, so they compose left to right:

let nums = [1, 2, 3, 4]
print(nums.filter { $0 % 2 == 0 }.map { $0 * $0 }.reduce(0, +))

//> 20

Read it as a pipeline: keep 2 and 4, square them to 4 and 16, add them to 20. Long chains are idiomatic Swift, but break them across lines once there are more than three stages — one operation per line is far easier to debug.

compactMap — transform and drop the failures

Any transform that returns an optional pairs naturally with compactMap, which unwraps the successes and discards the nils in one pass:

let raw = ["1", "2", "abc", "4"]
print(raw.map { Int($0) })
print(raw.compactMap { Int($0) })

//> [Optional(1), Optional(2), nil, Optional(4)]
//> [1, 2, 4]

map gives you [Int?] — the failure is still in there. compactMap gives you [Int]. This is the standard way to parse a list of strings that might not all be numbers.

Asking questions instead of building collections

Not every higher-order method returns a collection:

let nums = [1, 2, 3, 4, 5]

print(String(describing: nums.first(where: { $0 > 3 })))
print(nums.contains(where: { $0 > 4 }))
print(nums.allSatisfy { $0 > 0 })
print([Int]().allSatisfy { $0 > 0 })

//> Optional(4)
//> true
//> true
//> true

Two things to file away. first(where:) returns an optional, because there may be no match. And allSatisfy on an empty collection is true — there is no element that breaks the rule, so the claim holds vacuously. That last one is a genuine source of bugs when a filter unexpectedly empties a list.

✓ / ✗ — idiom and trap side by side

Idiomatic

let ints = ["3", "x", "7"].compactMap { Int($0) }
print(ints.reduce(0, +))

Same intent, three problems

let ints = ["3", "x", "7"].map { Int($0) }.filter { $0 != nil }.map { $0! }

Three passes over the data instead of one, a force-unwrap that a future edit can invalidate, and a line nobody reads at a glance. compactMap exists precisely to delete it.

Your exercise

Read a line of space-separated integers, keep the even ones, square them, and print the sum.

The mistake the grader catches is skipping the squaring step. Chaining .filter { $0 % 2 == 0 }.reduce(0, +) straight through sums the evens themselves — 2 + 4 = 6 on the first visible test, where the answer is 4 + 16 = 20. Put the map between the filter and the reduce. The hidden test 1 3 5 7 has no even numbers at all and expects 0, which reduce(0, +) handles for free — but any approach reaching for .max(), .first! or nums[0] on the filtered array crashes there instead.

Up nextStructs vs ClassesTypes, Enums, and Errors

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…