Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

array
An ordered list of values of one type, reached by position starting at zero.
block
A group of lines of code that run together, often inside curly brackets.
closure
A piece of code you can store in a value and pass to other code, remembered with the names around it.
compiler
The program that turns the code you write into something the machine can run, reporting mistakes before it does.
dictionary
A collection of key and value pairs, where each key appears once and finds its value.
enum
A type whose value is one of a fixed set of named cases, which may each carry extra data.
expression
A piece of code that produces a value, such as a sum or a call to a function.
function
A named piece of code that takes values and returns a result when called.
guard
A check that must pass before the code after it runs, leaving the scope early when it fails.
method
A function that belongs to a type and is called on a value of that type.
nil
The absence of a value, allowed only where a type says a value may be missing.
operator
A symbol such as plus or equals that combines values into a new one.
optional
A type that either holds a value or holds nothing, written with a question mark after the type.
optional chaining
Writing a question mark before something you reach into, so the result is nil when the value is missing.
property
A named value that belongs to a type or to one of its values.
protocol
A list of requirements that a type promises to meet, so code can work with any type that meets them.
scope
The region of code where a name can be used before it goes out of reach.
string interpolation
Putting a value inside text with a backslash and parentheses, so the value becomes part of the text.
struct
A type that groups related values and makes a copy whenever it is given another name or passed along.
subscript
A way to read or write an item in an array or dictionary by putting a key or position in square brackets.
type
What kind of value something is, such as a whole number or text, and what it can do.
unwrap
To get the value out of an optional after checking that something is there.

Swift from the ground up

Capstone: a records report

Modeling a record as a struct, keeping only the ones that qualify, sorting them, and printing a tidy report.

Part 12 of 12

Advanced20 min readBack to Courses

The shape of the data

A report starts with a record. struct Record with a name, a score and a team gives every row the same three fields, so the rest of the program can rely on them.

A list of those rows is your data set. Building it with a literal keeps this example self-contained: nothing is read from a file or a network, so the output is the same every time.

Each field is a let, which says a record does not change after it is built. The member-wise initialiser takes the fields in the order they are declared, so Record(name:score:team:) must be called with the labels in that order. If a later report needs to edit a score, build a new record instead of mutating the old one; the original list then remains valid for a second summary.

Keep, then order

Two small steps turn the raw list into a report. filter keeps the rows you want, and sorted puts them in order. Each takes a short closure that says what to do with one row.

The sort block compares two rows, written $0 and $1. $0.score > $1.score puts the highest score first, which is the order a reader expects in a ranking.

filter keeps the original order of the rows it accepts, and sorted then reorders them. Swift does not promise that equal elements keep their earlier order, so two rows with the same score may swap between runs. A tie-breaker on the name makes the output repeatable, which matters when a report is compared across runs.

Print it, then summarise

A for loop over the sorted rows prints one line each, using interpolation to place the name, the team and the score where they belong.

A single reduce folds the whole list into a total, and dividing by the count gives the average. Together the lines show the headline number and the row-by-row detail behind it.

The average here is a whole number because total and records.count are both Int. To show a decimal, convert the total before dividing: Double(total) / Double(records.count). If the list could be empty, check for that first; dividing by a count of zero stops the program rather than returning a sensible default.

A pipeline is read from the top down

Each step hands a new collection to the next: filter keeps rows, sorted orders them, and map can turn each row into a line. Nothing is changed in place, so the original list is still available for a second report.

When two rows tie, add a second comparison. $0.score != $1.score ? $0.score > $1.score : $0.name < $1.name breaks the tie by name; without it, equal scores may come back in either order.

A stable tie-breaker is not decoration; it is what makes two runs of the report agree.

A map can turn each record into its printed line, and joined(separator: "\n") can put the lines together with newlines between them. That is the same report as the loop, produced as one string. The loop is easier to read while debugging; the joined string is what you would write to a file or send somewhere else.

What goes wrong

A filter placed after a sort still works, but it wastes the ordering work on rows that are then discarded. Put the cheapest narrowing step first and sort what survives. The same rule applies to a chain of maps: each step runs over the whole intermediate collection.

A sort block that uses >= is not a valid ordering, because two equal rows both claim to come first. The result can vary between runs or even between machines. Use > and add a tie-breaker. And a first on a filtered list returns an optional, so best?.name ?? "none" is the safe way to print it.

The two moves that build a report

struct Record {
    let name: String
    let score: Int
}

let rows = [
    Record(name: "Ada", score: 92),
    Record(name: "Alan", score: 78)
]

let top = rows.filter { $0.score >= 80 }
    .sorted { $0.score > $1.score }

for row in top {
    print(row.name, row.score)
}

Three mistakes distort a report. An average built with Int division drops the fraction, so convert to Double when the decimal matters. A sort block must be a strict order: use > rather than >=, or equal rows each claim to come first and the result is undefined. And a dictionary lookup is optional even when the key came from the dictionary’s own keys, so prefer ?? 0 to a forced unwrap.

A ranked score report

Run it and read the ranked lines and the average. Then change the passing mark in the filter and predict how the count and the ranked lines change.

Output
Click "Run" to execute your code

Try these next

  • Add a tie-breaker to the ranking so equal scores sort by name.
  • Print a team summary that groups rows with a dictionary and sorts the team names.
  • Compute the average as a Double and compare it with the whole-number version.

A summary for each team

Run it and check each team’s total and best scorer. Then add a sixth record and predict which team line changes and by how much.

Output
Click "Run" to execute your code

Each piece of this report is a tool you met earlier: a struct from one chapter, the filter and sort from another. Building a report is mostly choosing which of them to line up, in the right order.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Why model each row as a struct?