Swift Module 3 – Strings, text and collections
map, filter, reduce and lazy sequences
Write Swift loops as map, filter, reduce, compactMap and flatMap, rank with sorted, min and prefix, and count the closure calls a lazy pipeline saves.
What you will learn
- Transform collections with map, filter, reduce(into:), compactMap and flatMap
- Sort, search and combine sequences with sorted(by:), min(by:), prefix and zip, breaking ties on purpose
- Use lazy sequences to avoid intermediate arrays and stop early
- Predict how many times the closures of an eager and a lazy pipeline run
Before you start
What you’ll learn · Before you start
What you will learn
- Transform collections with map, filter, reduce(into:), compactMap and flatMap
- Sort, search and combine sequences with sorted(by:), min(by:), prefix and zip, breaking ties on purpose
- Use lazy sequences to avoid intermediate arrays and stop early
- Predict how many times the closures of an eager and a lazy pipeline run
Before you start
On this page
Finding the first six in a 120-ball innings, a pipeline of filter and map ran its test on all 120 balls, while the
same pipeline made lazy stopped after 10. Both gave the same answer, over 2; one did twelve times the work, and built
an array of every six on the way.
Here you will replace loops with map, filter, reduce, compactMap and flatMap, rank and search with sorted,
min and prefix, breaking ties on purpose, and count the A function written as code in braces that can be passed to another function, which runs it later: { $0.runs > 0 } takes one value, named $0, and says whether its runs are above 0. The module on closures covers them in full. calls that a lazy pipeline saves, and the ones it
repeats.
map, filter and reduce replace three kinds of loop
Three loops come up again and again: one that changes every item, one that keeps some items, and one that adds them
up. Each has a method that takes the loop’s body as a closure, the code in braces that the method runs for
every item, with $0 standing for the item. This program works on the twelve balls of two overs:
// transform.swift: map, filter and reduce on the balls of two overs.
struct Ball {
let over: Int
let batter: String
let runs: Int
}
let balls = [
Ball(over: 1, batter: "Asha", runs: 1), Ball(over: 1, batter: "Ravi", runs: 4),
Ball(over: 1, batter: "Ravi", runs: 0), Ball(over: 1, batter: "Ravi", runs: 2),
Ball(over: 1, batter: "Ravi", runs: 1), Ball(over: 1, batter: "Asha", runs: 0),
Ball(over: 2, batter: "Ravi", runs: 6), Ball(over: 2, batter: "Ravi", runs: 0),
Ball(over: 2, batter: "Ravi", runs: 1), Ball(over: 2, batter: "Asha", runs: 4),
Ball(over: 2, batter: "Asha", runs: 0), Ball(over: 2, batter: "Asha", runs: 1),
]
let runs = balls.map(\.runs)
print("runs:", runs)
let scoring = balls.filter { $0.runs > 0 }.map(\.runs)
print("scoring balls:", scoring.count, "of", balls.count, scoring)
let total = scoring.reduce(0, +)
print("total:", total)
let byBatter = balls.reduce(into: [String: Int]()) { totals, ball in
totals[ball.batter, default: 0] += ball.runs
}
for (batter, score) in byBatter.sorted(by: { $0.key < $1.key }) {
print(batter, score)
}
let boundaries = balls.filter { $0.runs >= 4 }.map { "\($0.batter) \($0.runs)" }
print("boundaries:", boundaries) Output
runs: [1, 4, 0, 2, 1, 0, 6, 0, 1, 4, 0, 1] scoring balls: 8 of 12 [1, 4, 2, 1, 6, 1, 4, 1] total: 20 Asha 6 Ravi 14 boundaries: ["Ravi 4", "Ravi 6", "Asha 4"]
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O transform.swift -o transform && ./transform
map returns a new array with the closure’s result for every item: map(\.runs) takes each ball’s runs, because a
key path: An expression such as \Ball.runs, or \.runs where the type is known, that names a property. It reads that property from any value of the type, and can stand for a function, as in map(\.runs). can stand for the function that reads it (SE-0249). filter keeps the items for which the closure returns
true, and reduce(0, +) starts from 0 and adds each item in turn. A closure that is the last argument may follow the
parentheses, so filter { $0.runs > 0 } is filter({ $0.runs > 0 }). Step through the chain that kept the scoring
balls and added them up:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Twelve balls. balls holds the twelve balls of two overs, each with its over, its batter and its runs.
- filter keeps eight. filter runs its closure on all 12 balls and returns a new array of the 8 for which runs > 0 is true. The four dot balls are left out.
- map takes the runs. map(\.runs) calls the key path on each of the 8 balls and returns a new array of numbers: 1, 4, 2, 1, 6, 1, 4, 1.
- reduce adds them. reduce(0, +) starts from 0 and adds each number in turn: 1, 5, 7, 8, 14, 15, 19, 20. The result is one value, 20.
- Two arrays, then one answer. filter and map each built a whole new array before the next stage began, 8 balls and then 8 numbers, and reduce turned the numbers into one value. The lazy section shows a way to skip those arrays.
reduce(into:) suits a result that is itself a collection: its closure gets the result so far as an inout
parameter and changes it in place, here a dictionary of runs by batter. The documentation of reduce(into:)
recommends it over reduce(_:_:) whenever the result is a copy-on-write type such as an Array or a Dictionary.
Building an array with reduce(_:_:)
scores.reduce([]) { $0 + [$1] } makes a new array at every step, copying everything collected so far, so n items
cost about n²/2 copies. reduce(into: []) { $0.append($1) } appends in place, and map or filter is clearer
still when one of them says what you mean.
Checkpoint
Check: map, filter and reduce
compactMap drops the nils and flatMap joins the pieces
Scores typed by hand include mistakes: W for a wicket, an empty field, a word. Int("W") is nil, so mapping with
Int(_:) gives an array of optionals, and compactMap keeps only the values that are there:
// compact_flat.swift: compactMap drops the nils, flatMap joins nested arrays.
let typed = ["4", "W", "6", "", "1", "two"]
let parsed = typed.map { Int($0) }
print("map:", parsed)
let scores = typed.compactMap { Int($0) }
print("compactMap:", scores)
let overs = [[1, 4, 0, 2, 1, 0], [6, 0, 1, 4, 0, 1]]
print("map:", overs.map { $0.reduce(0, +) })
print("flatMap:", overs.flatMap { $0 })
print("flatMap, then filter:", overs.flatMap { $0 }.filter { $0 >= 4 }) Output
map: [Optional(4), nil, Optional(6), nil, Optional(1), nil] compactMap: [4, 6, 1] map: [8, 12] flatMap: [1, 4, 0, 2, 1, 0, 6, 0, 1, 4, 0, 1] flatMap, then filter: [4, 6, 4]
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O compact_flat.swift -o compact_flat && ./compact_flat
Step through what compactMap does with each of the six inputs:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Six strings. typed holds six strings. Int(_:) turns a string of digits into an Int and returns nil for anything else.
- "4" gives 4. The closure returns Optional(4). It holds a value, so compactMap unwraps it and appends 4.
- "W" gives nil. W is a wicket, not a number, so Int("W") is nil. compactMap drops it; map would have kept a nil in its place.
- "6" gives 6. 6 parses, so it is appended as the second value.
- The rest. The empty string and "two" give nil and are dropped; "1" gives 1, the third value.
- [4, 6, 1]. compactMap returned [4, 6, 1]: the values that were there, in their order, as plain Ints with no optionals left.
map kept a slot for every input, three of them nil; compactMap returned [4, 6, 1], the parsed numbers in their
order. flatMap is the same idea for closures that return arrays: it joins the pieces, so the two overs became one list
of twelve balls, which filter then searched for boundaries. map on the same overs gave one total per over instead,
8 and 12. Choose by the shape you want back: one result per item, the results that are not nil, or every element of
every result.
Checkpoint
Check: compactMap and flatMap
sorted, min, max and prefix answer questions about order
sorted(by:) returns a new array ordered by a closure that says whether its first argument belongs before its
second; sort(by:) sorts an array in place. This program ranks five players by runs:
// ordering.swift: sorted with a tie-breaker, min and max, prefix, first(where:) and zip.
let players = [("Asha", 45), ("Ravi", 52), ("Meera", 45), ("Kabir", 52), ("Zoya", 38)]
let byRuns = players.sorted { $0.1 > $1.1 }
print("by runs:", byRuns.map(\.0))
let ranked = players.sorted { $0.1 != $1.1 ? $0.1 > $1.1 : $0.0 < $1.0 }
print("by runs, then name:", ranked.map(\.0))
print("top two:", ranked.prefix(2).map(\.0))
print("lowest:", players.min { $0.1 < $1.1 }?.0 ?? "none")
print("first under 40:", players.first { $0.1 < 40 }?.0 ?? "none")
print("ranked while 50 or more:", ranked.prefix(while: { $0.1 >= 50 }).map(\.0))
let positions = zip(1..., ranked).map { "\($0). \($1.0)" }
print(positions)
let numbered = ranked.enumerated()
print("count:", numbered.count, "last:", numbered.last.map { "\($0.offset) \($0.element.0)" } ?? "none") Output
by runs: ["Ravi", "Kabir", "Asha", "Meera", "Zoya"] by runs, then name: ["Kabir", "Ravi", "Asha", "Meera", "Zoya"] top two: ["Kabir", "Ravi"] lowest: Zoya first under 40: Zoya ranked while 50 or more: ["Kabir", "Ravi"] ["1. Kabir", "2. Ravi", "3. Asha", "4. Meera", "5. Zoya"] count: 5 last: 4 Zoya
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O ordering.swift -o ordering && ./ordering
Sorting by runs alone put Ravi before Kabir and Asha before Meera, the order they had in the input. Swift’s sort is
stable sort: A sort that keeps elements its comparison calls equal in the order they had before. Swift documents sort(by:) and sorted(by:) as stable, so a tie keeps its input order.: the sorted(by:) documentation says it “preserves the relative order of elements” that the closure does not
order (SE-0372). A ranking that should not depend on input order needs a tie-breaker, here the name. Step through both
sorts:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Two ties. Ravi and Kabir both have 52, and Asha and Meera both have 45. The closure { $0.1 > $1.1 } says nothing about the order inside a tie.
- By runs alone. sorted is stable, so each tie keeps the input order: Ravi came before Kabir, and Asha before Meera.
- Change the input. Had the input listed Kabir before Ravi, the same sort would rank Kabir first. The ranking depends on where the data came from.
- Add a tie-breaker. The second sort compares names when the runs are equal: $0.1 != $1.1 ? $0.1 > $1.1 : $0.0 < $1.0. Kabir now comes before Ravi, and Asha before Meera, whatever the input order.
- Then prefix and zip. prefix(2) takes Kabir and Ravi, and zip(1..., ranked) numbers the ranking from 1.
prefix(2) took the top two, min(by:) found the lowest score and returns an optional, nil for an empty array, and
first(where:) and prefix(while:) stop at the first item that settles the question. zip(1..., ranked) numbered
the ranking by pairing it with a one-sided range, and enumerated() numbered it from 0.
Two compile-time surprises come with these tools. Since Swift 6.2 (SE-0459) the result of enumerated() on an
array is a collection too, so .count and .last work on it, but on macOS that conformance needs version 26 at run
time. And KeyPathComparator, a comparator that sorts by a key path, belongs to Foundation, not to the standard
library. This session builds the three files below:
enumerated_old.swift
// enumerated_old.swift: the result of enumerated() used as a Collection.
let numbered = ["Kabir", "Ravi", "Asha"].enumerated()
print(numbered.count) keypath_sort.swift
// keypath_sort.swift: KeyPathComparator without import Foundation.
struct Player {
let name: String
let runs: Int
}
let players = [Player(name: "Asha", runs: 45), Player(name: "Ravi", runs: 52)]
print(players.sorted(using: KeyPathComparator(\Player.runs, order: .reverse)).map { $0.name }) keypath_fixed.swift
// keypath_fixed.swift: the same sort with import Foundation.
import Foundation
struct Player {
let name: String
let runs: Int
}
let players = [Player(name: "Asha", runs: 45), Player(name: "Ravi", runs: 52)]
print(players.sorted(using: KeyPathComparator(\Player.runs, order: .reverse)).map { $0.name }) from session import run, version
# enumerated_old.swift built for macOS 15 in both language modes, then for this Mac; KeyPathComparator without and
# with Foundation.
version()
run("""
swiftc -target arm64-apple-macosx15.0 enumerated_old.swift -o enumerated_old
!swiftc -swift-version 6 -target arm64-apple-macosx15.0 enumerated_old.swift -o enumerated_old
swiftc enumerated_old.swift -o enumerated_old
./enumerated_old
!swiftc keypath_sort.swift -o keypath_sort
swiftc keypath_fixed.swift -o keypath_fixed
./keypath_fixed
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -target arm64-apple-macosx15.0 enumerated_old.swift -o enumerated_old
enumerated_old.swift:3:16: warning: conformance of 'EnumeratedSequence<Base>' to 'Collection' is only available in macOS 26.0 or newer; this is an error in the Swift 6 language mode
1 | // enumerated_old.swift: the result of enumerated() used as a Collection.
2 | let numbered = ["Kabir", "Ravi", "Asha"].enumerated()
3 | print(numbered.count)
| |- warning: conformance of 'EnumeratedSequence<Base>' to 'Collection' is only available in macOS 26.0 or newer; this is an error in the Swift 6 language mode
| `- note: add 'if #available' version check
4 |
$ swiftc -swift-version 6 -target arm64-apple-macosx15.0 enumerated_old.swift -o enumerated_old
enumerated_old.swift:3:16: error: conformance of 'EnumeratedSequence<Base>' to 'Collection' is only available in macOS 26.0 or newer
1 | // enumerated_old.swift: the result of enumerated() used as a Collection.
2 | let numbered = ["Kabir", "Ravi", "Asha"].enumerated()
3 | print(numbered.count)
| |- error: conformance of 'EnumeratedSequence<Base>' to 'Collection' is only available in macOS 26.0 or newer
| `- note: add 'if #available' version check
4 |
[exit status 1]
$ swiftc enumerated_old.swift -o enumerated_old
$ ./enumerated_old
3
$ swiftc keypath_sort.swift -o keypath_sort
keypath_sort.swift:7:29: error: cannot find 'KeyPathComparator' in scope
5 | }
6 | let players = [Player(name: "Asha", runs: 45), Player(name: "Ravi", runs: 52)]
7 | print(players.sorted(using: KeyPathComparator(\Player.runs, order: .reverse)).map { $0.name })
| `- error: cannot find 'KeyPathComparator' in scope
8 |
[exit status 1]
$ swiftc keypath_fixed.swift -o keypath_fixed
$ ./keypath_fixed
["Ravi", "Asha"]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 compile_session.py
Built for macOS 15, numbered.count is a warning in the Swift 5 language mode and an error in Swift 6 mode; built for
this Mac’s macOS 26, it prints 3. Without import Foundation the compiler cannot find KeyPathComparator; with it,
the sort works.
Key idea
Sorting is stable, so equal items keep their input order: when a ranking must not depend on that order, sort by a second key that breaks every tie.
Checkpoint
Check: Sorting, ties and stopping early
A lazy pipeline works one item at a time, only when asked
An eager filter runs its closure on every item and returns a new array before the next step starts. .lazy gives a
lazy sequence: A sequence, made with .lazy, whose map, filter and similar operations store their closures and run them only when an element is read. It builds no arrays on the way, and runs the closures again on every read., whose map and filter store their closures instead, and run them only when someone asks for an element.
This program finds the over of the first six in 120 balls both ways, and counts the calls:
// lazy_counts.swift: how often the closures of an eager and a lazy pipeline run.
let pattern = [0, 1, 0, 4, 1, 0, 2, 0, 1, 6, 0, 1]
let runs = (0..<120).map { pattern[$0 % pattern.count] } // 120 balls: 20 overs of 6
var sixChecks = 0
var overCalls = 0
let isSix = { (ball: Int) -> Bool in
sixChecks += 1
return runs[ball] == 6
}
let overOf = { (ball: Int) -> Int in
overCalls += 1
return ball / 6 + 1
}
let eager = runs.indices.filter(isSix).map(overOf).first
print("eager: first six in over", eager ?? 0, "| isSix ran", sixChecks, "| overOf ran", overCalls)
sixChecks = 0
overCalls = 0
let lazy = runs.indices.lazy.filter(isSix).map(overOf).first
print("lazy: first six in over", lazy ?? 0, "| isSix ran", sixChecks, "| overOf ran", overCalls)
sixChecks = 0
let sixes = runs.indices.lazy.filter(isSix)
print("sixes:", sixes.count, "| isSix ran", sixChecks)
print("sixes again:", Array(sixes).count, "| isSix ran", sixChecks) Output
eager: first six in over 2 | isSix ran 120 | overOf ran 10 lazy: first six in over 2 | isSix ran 10 | overOf ran 1 sixes: 10 | isSix ran 120 sixes again: 10 | isSix ran 240
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O lazy_counts.swift -o lazy_counts && ./lazy_counts
The eager version checked all 120 balls, built an array of the 10 sixes and worked out the over of each, only to keep
the first. The lazy version checked balls until it found the first six, at ball 10, and called overOf once. Step
through how first pulls one ball at a time through the lazy pipeline:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- first asks. first needs one element, so it asks the lazy pipeline for its next one. Nothing has run yet: making the pipeline ran no closure.
- Ball 0. The filter asks the indices for one, gets 0 and runs isSix(0). Ball 0 is a dot, so the filter asks again.
- Balls 1 to 8. Balls 1 to 8 are not sixes either. isSix has now run 9 times, and overOf not at all.
- Ball 9 is a six. Index 9 holds the first 6, so the filter passes it to map, which runs overOf(9) once: 9 / 6 + 1 is over 2.
- first is done. The pipeline hands over 2 to first, which stops asking. Indices 10 to 119 are never read, and no array was built.
- The eager way. The eager filter ran isSix on all 120 balls and built an array of 10 sixes, then map ran overOf 10 times, and first kept only the first result.
The LazySequenceProtocol documentation describes exactly this: each time an element of a lazy sequence is accessed,
its closure runs on an element of the base. That cuts both ways. The last two lines read the same lazy filter twice,
once for count and once to build an array, and isSix ran 120 times for each, 240 in all. A lazy result is a recipe,
not a stored answer.
Reading a lazy sequence again and again
Every pass over a lazy map or filter runs its closures again, and a closure with a side effect, such as a counter
or a log line, repeats it. Turn the result into an Array once, let sixes = Array(…lazy.filter(isSix)), when you will
read it more than once.
Checkpoint
Check: Lazy pipelines and their closure calls
Practise: statistics from an innings
From the balls of three overs, print each batter’s runs and strike rate (runs per 100 balls, to one decimal), every
batter with the top score, ties included, and the first over in which 15 or more runs were scored. Write each answer as
a pipeline: reduce(into:) for the totals, map and sorted for the report, filter for the ties and first(where:)
for the over.
Write and test yours before reading this version:
// practice.swift: strike rates, top scorers with ties and the first big over, written as pipelines.
struct Ball {
let over: Int
let batter: String
let runs: Int
}
let pattern: [(String, Int)] = [
("Asha", 1), ("Ravi", 4), ("Ravi", 0), ("Ravi", 1), ("Asha", 2), ("Asha", 0),
("Meera", 6), ("Meera", 1), ("Asha", 4), ("Asha", 0), ("Asha", 1), ("Meera", 0),
("Ravi", 6), ("Ravi", 4), ("Ravi", 1), ("Meera", 2), ("Meera", 4), ("Meera", 3),
]
let innings = pattern.enumerated().map { Ball(over: $0.offset / 6 + 1, batter: $0.element.0, runs: $0.element.1) }
let faced = innings.reduce(into: [String: (runs: Int, balls: Int)]()) { tally, ball in
tally[ball.batter, default: (0, 0)].runs += ball.runs
tally[ball.batter, default: (0, 0)].balls += 1
}
let rates = faced.map { batter, t in (batter, t.runs, (t.runs * 1000 + t.balls / 2) / t.balls) }
for (batter, runs, tenths) in rates.sorted(by: { $0.0 < $1.0 }) {
print(batter, runs, "runs, strike rate \(tenths / 10).\(tenths % 10)")
}
let best = faced.values.map(\.runs).max() ?? 0
print("top score \(best):", faced.filter { $0.value.runs == best }.keys.sorted())
let overTotals = Dictionary(grouping: innings, by: \.over).mapValues { $0.map(\.runs).reduce(0, +) }
let firstBig = overTotals.keys.sorted().lazy.first { overTotals[$0, default: 0] >= 15 }
print("over totals:", overTotals.sorted(by: { $0.key < $1.key }).map(\.value))
print("first over of 15 or more:", firstBig.map(String.init) ?? "none") Output
Asha 8 runs, strike rate 133.3 Meera 16 runs, strike rate 266.7 Ravi 16 runs, strike rate 266.7 top score 16: ["Meera", "Ravi"] over totals: [8, 12, 20] first over of 15 or more: 3
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O practice.swift -o practice && ./practice
reduce(into:) collects runs and balls per batter in one pass over the innings, and the strike rate is worked out in
tenths with integers, so 16 runs from 6 balls prints 266.7. Meera and Ravi share the top score, so filter keeps both,
sorted by name. Over 3 is the first with 15 or more runs, 20. The practice round below picks your weakest idea first,
and each right answer brings a question one level up:
Practice that adapts to you
Practise this lesson
13 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what is the difference between map, compactMap and flatMap?
All three call a closure on every element and return an array, and they differ in what they do with the closure’s
results. map keeps one result per element, so mapping with a closure that returns an optional gives an array of
optionals, such as [Optional(4), nil, Optional(6)]. compactMap unwraps the results and drops the nils, so
["4", "W", "6"].compactMap { Int($0) } is [4, 6]. flatMap is for closures that return a sequence: it joins all
the returned sequences into one array, so [[1, 4], [6]].flatMap { $0 } is [1, 4, 6]. Older Swift also had a
flatMap that removed nils; it was renamed compactMap (SE-0187) because the two jobs are different. A quick test is
the shape you need back: one result per element, only the values that exist, or every element of every result.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A view model stores orders.lazy.filter(isSuspicious).map(enrich) in a property. The screen reads its count, then shows the list. Logs show enrich running twice for many orders, and isSuspicious, which calls a fraud service, uses up the service's quota early. What is happening, and how do you fix it?
-
Fresher to mid
Rank players by runs, highest first, so that tied players share a rank, as in 1, 2, 2, 4, and players inside a tie are listed by name. Describe the Swift code, explain why the order must not depend on the input order, and give its cost.
Key takeaways
maptransforms every element,filterkeeps those that pass a test, andreducecombines them;reduce(into:)builds an array or dictionary in place, which the documentation recommends for copy-on-write results.compactMapkeeps only the non-nil results of a closure, andflatMapjoins the sequences a closure returns.sorted(by:)is stable, so a ranking that must not depend on input order needs a tie-breaker;min(by:)andmax(by:)return optionals, andprefix,first(where:)andprefix(while:)stop early..lazymakesmapandfilterrun their closures only when an element is asked for, so a search can stop at the first match without building arrays.- A lazy sequence runs its closures again on every pass, so turn it into an Array when it will be read more than once.
Terms in this lesson
- key path
- An expression such as \Ball.runs, or \.runs where the type is known, that names a property. It reads that property from any value of the type, and can stand for a function, as in map(\.runs).
- lazy sequence
- A sequence, made with .lazy, whose map, filter and similar operations store their closures and run them only when an element is read. It builds no arrays on the way, and runs the closures again on every read.
- stable sort
- A sort that keeps elements its comparison calls equal in the order they had before. Swift documents sort(by:) and sorted(by:) as stable, so a tie keeps its input order.
References
- The Swift Programming Language (6.4): Closures (Swift.org)
- Swift standard library: Sequence reduce(into:_:) (Swift.org)
- Swift standard library: Sequence compactMap(_:) (Swift.org)
- Swift standard library: Sequence sorted(by:) (Swift.org)
- Swift standard library: LazySequenceProtocol (Swift.org)
- SE-0187: Introduce Sequence.compactMap(_:) (Swift Evolution (The Swift Project))
- SE-0249: Key Path Expressions as Functions (Swift Evolution (The Swift Project))
- SE-0372: Document Sorting as Stable (Swift Evolution (The Swift Project))
- SE-0459: Add Collection conformances for enumerated() (Swift Evolution (The Swift Project))
- KeyPathComparator (Apple)
Report a problem with this lesson
Kept only in this browser. Your Learn progress