Tutorial outline

Unit 1

  1. 01Getting started
  2. 02Names and functions
  3. 03Control
  4. 04Higher-order functions
  5. 05Environments and closures
  6. 06Functional abstraction
  7. 07Recursion
  8. 08Tree recursion

Unit 2

  1. 09Sequences
  2. 10Dictionaries, sets, and tuples
  3. 11Optionals
  4. 12Structures and enumerations
  5. 13Linked lists and trees
  6. 14Mutation and value semantics
  7. 15Classes and inheritance
  8. 16Protocols and generics
  9. 17Iterators and lazy sequences
  10. 18Efficiency and memoization

Unit 3

  1. 19Algebraic data types and pattern matching
  2. 20Writing an interpreter
  3. 21Error handling
  4. 22Concurrency with async and await
  5. 23Testing
  6. 24Building a Swift package

Sequences

Arrays, ranges, and strings as sequences: length, element selection, membership, slicing, and the processing patterns that work on all of them.

CS61A
Sequences · Composing Programs 2.1–2.3

Unit 1 was about functions. Unit 2 is about data: how to put many values together, and how to build new kinds of values of your own. It starts with the most common way to combine values, putting them in order.

A sequence is an ordered collection of values. CS61A describes the sequence abstraction by what you can do with one, not by how it is stored:

  • Length: how many elements it has.
  • Element selection: get the element at a position.
  • Membership: ask whether a value is in it.
  • Slicing: take a contiguous part of it.

Arrays, ranges, and strings are all sequences in Swift, and all of them support these operations.

Arrays

An array holds values of one type, in order. [Int] is the type of an array of integers:

let primes = [2, 3, 5, 7, 11]
print(primes.count)          // 5, the length
print(primes[0], primes[4])  // 2 11, element selection
print(primes.contains(9))    // false, membership
print(primes.first!, primes.isEmpty)   // 2 false

Positions start at 0, and asking for a position that does not exist stops the program, as Lesson 1 showed. + joins two arrays into a new one:

print([1, 2] + [3])   // [1, 2, 3]

An array bound with var can grow and change:

var numbers = [3, 1]
numbers.append(4)          // [3, 1, 4]
numbers.insert(0, at: 0)   // [0, 3, 1, 4]
numbers += [5]             // [0, 3, 1, 4, 5]
print(numbers, numbers.firstIndex(of: 4)!)   // [0, 3, 1, 4, 5] 3

The exclamation mark after first and firstIndex(of:) is there because both might have no answer: an empty array has no first element. Lesson 11 is about how Swift represents “no answer”.

Iterating

A for-in loop visits the elements in order. enumerated() gives each element with its position, and zip walks two sequences side by side:

for (index, name) in ["Ada", "Grace"].enumerated() {
    print(index, name)
}
for (number, letter) in zip([1, 2, 3], ["a", "b", "c"]) {
    print(number, letter)
}

Slicing

A slice is a contiguous part of a sequence, written with a range:

let values = [10, 20, 30, 40]
print(values[1...2])   // [20, 30]
print(values[..<2])    // [10, 20]
print(values.prefix(2), values.suffix(1), values.dropFirst())

Swift’s slices come with a surprise. A slice of an array is an ArraySlice, a view of the original array that keeps the original positions. It does not renumber from zero.

What would Swift print?

let values = [10, 20, 30, 40]
let middle = values[1...2]
print(middle[1], middle.startIndex)

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

Answer20 1

middle holds 20 and 30 at their original positions, 1 and 2. So middle[1] is 20, not 30, and its first valid index is 1. Wrap it in Array(middle) to get a fresh array numbered from 0.

Strings

A string is a sequence of Characters. It has a length, membership, and iteration like any sequence:

let word = "hello"
print(word.count, word.first!, word.contains("e"))   // 5 h true
for letter in word {
    print(letter, terminator: "-")
}
print()

What a string does not allow is selecting a character by an integer.

What would Swift print?

let word = "hello"
print(word[0])

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

AnswerError

Swift rejects word[0] at compile time: “cannot subscript String with an Int”. Characters can take different amounts of memory (think of an emoji or an accented letter), so jumping to position k is not a constant-time operation, and Swift will not pretend it is. Use word.first, word.prefix(3), or convert with Array(word) when you need positions.

reversed() gives the characters in reverse order; String(...) turns them back into a string:

print(String("stressed".reversed()))   // desserts

Sequence processing

Lesson 4’s map, filter, and reduce work on every sequence, including ranges. Together they cover most of what CS61A does with list comprehensions:

let squares = (1...5).map { $0 * $0 }             // [1, 4, 9, 16, 25]
let evens = squares.filter { $0 % 2 == 0 }        // [4, 16]
let total = squares.reduce(0, +)                  // 55
print(squares, evens, total)
print([3, 1, 2].sorted(), Array(1...3))

What would Swift print?

let words = ["swift", "is", "fun"]
print(words.map { $0.count }.reduce(0, +), words.filter { $0.count > 2 })

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

Answer10 ["swift", "fun"]

The lengths are 5, 2, and 3, which add up to 10. The filter keeps the words longer than two characters.

Put the lines in order

Put the lines in order: digits(n) returns the digits of n as an array, and the program prints [2, 0, 2, 6].

  1. return [n]
  2. print(digits(2026))
  3. }
  4. if n < 10 {
  5. return digits(n / 10) + [n % 10]
  6. }
  7. func digits(_ n: Int) -> [Int] {

Show the correct program
func digits(_ n: Int) -> [Int] {
    if n < 10 {
        return [n]
    }
    return digits(n / 10) + [n % 10]
}
print(digits(2026))

Lab 8: Sequences

Q1: Couple

Write couple(_:_:), which takes two arrays of the same length and returns an array of two-element arrays, pairing elements at the same position.

func couple(_ first: [Int], _ second: [Int]) -> [[Int]] {
    // your code here
}
Show solution
func couple(_ first: [Int], _ second: [Int]) -> [[Int]] {
    precondition(first.count == second.count, "arrays must have the same length")
    return zip(first, second).map { [$0, $1] }
}

assert(couple([1, 2, 3], [4, 5, 6]) == [[1, 4], [2, 5], [3, 6]])
assert(couple([], []) == [])

zip produces pairs; map turns each pair into a two-element array.

Q2: Palindromes

Return true if a string reads the same forward and backward.

func isPalindrome(_ text: String) -> Bool {
    // your code here
}
Show solution
func isPalindrome(_ text: String) -> Bool {
    let characters = Array(text)
    return characters == characters.reversed()
}

assert(isPalindrome("racecar"))
assert(isPalindrome("noon"))
assert(!isPalindrome("swift"))
assert(isPalindrome(""))

Two arrays are equal when they have the same elements in the same order. The empty string is a palindrome: reversing nothing gives nothing.

Q3: Running totals

Write runningTotals(_:), which returns an array whose element at position i is the sum of the input’s elements at positions 0 through i.

func runningTotals(_ values: [Int]) -> [Int] {
    // your code here
}
Show solution
func runningTotals(_ values: [Int]) -> [Int] {
    var totals: [Int] = []
    var sum = 0
    for value in values {
        sum += value
        totals.append(sum)
    }
    return totals
}

assert(runningTotals([1, 2, 3, 4]) == [1, 3, 6, 10])
assert(runningTotals([5]) == [5])
assert(runningTotals([]) == [])

[Int] after var totals: is needed because an empty array literal alone does not say what type of element it will hold.

What’s next

Next lesson: containers that are not ordered by position, dictionaries and sets, and the tuples that hold a few values together.