Matua Doc

Other functions with closures

Learning intentions

In this lesson, you will explain what closures are and how they connect to array helpers. Recognise closure shorthand syntax and how to count closure arguments. Use safer helpers like compactMap, allSatisfy, and search methods to avoid manual loops.

Watch the overview video: Map, filter, and reduce in Swift

Closures in Swift

A closure is a function you can store in a variable or pass into another function. Array helpers like map, filter, and reduce take closures to do the work. If you need the basics of those helpers, see the previous page.

How to spot a closure parameter

Swift uses the arrow syntax (Input) -> Output to describe a closure type. If a function has a parameter like (Element) -> T, it expects a closure that takes one value and returns one value. These Apple docs show the real signatures: Array.map(_:), Array.filter(_:), and Array.reduce(::).

Shorthand arguments ($0, $1)

Swift lets you skip parameter names and use $0, $1, and so on. $0 is the first argument, $1 is the second. The number of arguments comes from the closure type in the function signature.

let numbers = [1, 2, 3, 4, 5]
let squares = numbers.map { $0 * $0 }
let evens = numbers.filter { $0 % 2 == 0 }
let sum = numbers.reduce(0) { $0 + $1 }

Safer array helpers

These helpers remove nil values, check conditions, and search safely without manual loops. Docs: Array.compactMap(_:), Sequence.allSatisfy(_:), Sequence.first(where:), Collection.firstIndex(where:), BidirectionalCollection.last(where:), Sequence.min(by:), and Sequence.max(by:).

compactMap: remove nil values

compactMap runs a transform and drops any nil results. This avoids force-unwrapping and extra checks.

let rawScores = ["42", "x", "100", "", "65"]
let scores = rawScores.compactMap { Int($0) }
print(scores) // [42, 100, 65]

allSatisfy: check every element

allSatisfy returns true only if every element matches the condition. This is clearer than tracking a manual isValid flag.

let ages = [13, 14, 15, 16]
let allTeenagers = ages.allSatisfy { $0 >= 13 && $0 <= 19 }
print(allTeenagers) // true

first(where:) and firstIndex(where:)

first(where:) returns the first element that matches. firstIndex(where:) returns the position of the first match. Both return optionals, which is safer than assuming a match exists.

let names = ["Aroha", "Moana", "Hana", "Pita"]
let firstLongName = names.first { $0.count >= 5 }
let firstLongIndex = names.firstIndex { $0.count >= 5 }
print(firstLongName ?? "none")
print(firstLongIndex ?? -1)

last(where:)

last(where:) finds the last element that matches a condition. This avoids reversing arrays or writing a backwards loop.

let temperatures = [18, 21, 19, 23, 17, 22]
let lastWarmDay = temperatures.last { $0 >= 20 }
print(lastWarmDay ?? 0) // 22

min(by:) and max(by:)

These find the smallest or largest element using a custom rule. They are safer than manual comparisons because the rules are explicit.

let cities = ["Auckland", "Wellington", "Christchurch"]
let shortest = cities.min { $0.count < $1.count }
let longest = cities.max { $0.count < $1.count }
print(shortest ?? "")
print(longest ?? "")

Task A: hidden numbers

You are given an array containing animal names and number strings.

Use compactMap to keep only the numbers and convert them to Int, then print the result.

Then, as an alternative check, use allSatisfy on the original array to test if every item is a number string.

let mixed = ["cat", "7", "owl", "15", "dog", "3"]

Task B: the midnight filter

You are given a list of sightings with a name and a danger score.

Use filter to keep only sightings where the name starts with "m" or "w".

Use map to extract the scores, then reduce to calculate the total.

Finally, use min(by:) and max(by:) to find the lowest and highest scores in the filtered set.

let sightings = [
    (name: "moth", score: 3),
    (name: "wolf", score: 9),
    (name: "raven", score: 4),
    (name: "mist", score: 7),
    (name: "wisp", score: 2)
]

Task C: safe input check

Create a function called accepts that takes a String and a closure parameter called isValid.

The closure should have the type (String) -> Bool and decide if the input passes.

Return true or false based on the closure.

Test your function with a predicate that only allows lowercase words and a predicate that only allows strings longer than 8 characters.

func accepts(_ input: String, isValid: (String) -> Bool) -> Bool {
    return isValid(input)
}

let sample = "moonlight"

Task D: the haunted archive

The archive has wings, each wing has rooms, each room has shelves, and each shelf holds words.

Find the first word that starts with "e" on the last shelf that contains an "e" word in the last room that contains any 4-letter word in the last wing that contains any word starting with "e".

Use first(where:) and last(where:) at each step, and print the final word.

let archive = [
    [
        [["candle", "dust"], ["mirror", "ash"]],
        [["whisper", "shadow"], ["clock", "veil"]]
    ],
    [
        [["stone", "key"], ["relic", "name"]],
        [["cipher", "bone"], ["ember", "seal"]]
    ],
    [
        [["feather", "ink"], ["glow", "eclipse"]],
        [["riddle", "echo"], ["ember", "glyph"]]
    ]
]

Summary

Closures let you pass functions into other functions. Shorthand arguments like $0 and $1 match the closure parameter count. Helpers like compactMap, allSatisfy, first, last, min, and max avoid risky manual loops.

Extension for Super Players!

Create your own data set and use map, filter, and reduce to answer a new question. Explain what you are trying to measure, and why each function helps.