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

Dictionaries, sets, and tuples

Look values up by key with dictionaries, test membership with sets, and group a few values with tuples, choosing the container that fits the question.

CS61A
Containers · Composing Programs 2.3

An array answers “what is at position 3?”. Many questions are not about positions: what is Ada’s phone number?, have we seen this word before?, what are the width and height? Swift has a container for each.

Dictionaries

A dictionary maps keys to values. [String: Int] maps strings to integers:

var ages = ["Ada": 36, "Grace": 85]
ages["Alan"] = 41          // add a new key
ages["Ada"] = 37           // replace a value
print(ages.count)          // 3
print(ages["Grace"]!)      // 85

Looking up a key that is not there is not an error. It gives nil, Swift’s “no value”, which is why the lookup above needs !. A lookup’s type is an optional, Int?, and printing it shows that:

What would Swift print?

let ages = ["Ada": 36]
print(ages["Ada"])
print(ages["Alan"])

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

Show answer and explanation

AnswerOptional(36)\nnil

ages["Ada"] is an Int? holding 36, which prints as Optional(36). There is no "Alan" key, so the second lookup is nil. Lesson 11 covers optionals properly; for now, read ? types as “maybe a value”.

Counting with a default

A lookup can supply a default for missing keys. That makes counting anything a two-line loop:

var counts: [String: Int] = [:]
for word in ["to", "be", "or", "not", "to", "be"] {
    counts[word, default: 0] += 1
}
print(counts["to"]!, counts["or"]!)   // 2 1

Order

A dictionary has no order. Iterating over one, or printing it, can list the pairs in any order, and the order can change between runs. When order matters, sort:

let stock = ["pears": 3, "apples": 5, "figs": 0]
for (fruit, count) in stock.sorted(by: { $0.key < $1.key }) {
    print(fruit, count)
}

Each element of a dictionary is a (key, value) pair, and the loop takes it apart into two names.

Sets

A set holds distinct values with no order. It answers one question quickly: is this value in here?

let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
print(vowels.contains("e"), vowels.contains("y"))   // true false

let seen = Set([3, 1, 3, 2, 1])
print(seen.count)   // 3, duplicates disappear
print(Set([1, 2]).union([2, 3]).sorted())   // [1, 2, 3]

What would Swift print?

let a: Set = [1, 2, 3]
let b: Set = [2, 3, 4]
print(a.intersection(b).sorted(), a.subtracting(b).count)

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

Show answer and explanation

Answer[2, 3] 1

The intersection holds the values in both sets, 2 and 3. Subtracting b from a leaves only 1, so its count is 1. Sorting first gives a predictable order to print.

contains on a set takes about the same time however large the set is. On an array it has to look at elements one by one. Lesson 18 measures the difference.

Tuples

A tuple groups a fixed number of values, possibly of different types. You met them in Lesson 2 as multiple return values:

let point = (x: 3, y: 4)
print(point.x, point.y)   // 3 4

let (a, b) = point        // take it apart
print(a + b)              // 7

A tuple is right for a small, fixed group that belongs together, like a coordinate or a result and its remainder. When the group grows, or needs behavior, Lesson 12’s structures are better.

What would Swift print?

func minMax(_ values: [Int]) -> (min: Int, max: Int) {
    (values.min()!, values.max()!)
}
let range = minMax([4, 9, 1, 7])
print(range.max - range.min)

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

Show answer and explanation

Answer8

The tuple holds 1 and 9, labeled min and max, and their difference is 8.

Put the lines in order

Put the lines in order. The function counts each letter; the program prints 3.

  1. for letter in text {
  2. print(letterCounts("banana")["a"]!)
  3. }
  4. func letterCounts(_ text: String) -> [Character: Int] {
  5. }
  6. counts[letter, default: 0] += 1
  7. var counts: [Character: Int] = [:]
  8. return counts

Show the correct program
func letterCounts(_ text: String) -> [Character: Int] {
    var counts: [Character: Int] = [:]
    for letter in text {
        counts[letter, default: 0] += 1
    }
    return counts
}
print(letterCounts("banana")["a"]!)

Lab 9: Containers

Q1: Word counts

Write wordCounts(_:), which splits a string on spaces and returns how many times each word appears. text.split(separator: " ") gives the words.

func wordCounts(_ text: String) -> [String: Int] {
    // your code here
}
Show solution
func wordCounts(_ text: String) -> [String: Int] {
    var counts: [String: Int] = [:]
    for word in text.split(separator: " ") {
        counts[String(word), default: 0] += 1
    }
    return counts
}

let counts = wordCounts("the cat and the hat")
assert(counts["the"] == 2)
assert(counts["cat"] == 1)
assert(counts["dog"] == nil)
assert(counts.count == 4)

split returns Substrings, views into the original string like the ArraySlice from last lesson. String(word) makes each an independent string to use as a key.

Q2: First occurrences

Write uniqueInOrder(_:), which removes repeated values from an array but keeps each value where it first appeared.

func uniqueInOrder(_ values: [Int]) -> [Int] {
    // your code here
}
Show solution
func uniqueInOrder(_ values: [Int]) -> [Int] {
    var seen: Set<Int> = []
    var result: [Int] = []
    for value in values where !seen.contains(value) {
        seen.insert(value)
        result.append(value)
    }
    return result
}

assert(uniqueInOrder([3, 1, 3, 2, 1]) == [3, 1, 2])
assert(uniqueInOrder([]) == [])
assert(uniqueInOrder([5, 5, 5]) == [5])

Array(Set(values)) would remove duplicates too, but sets have no order, so it would lose the order the problem asks for. The set is only for fast membership; the array keeps the order.

Q3: Group by first letter

Write groupByFirstLetter(_:), which maps each first letter to the words that start with it, in their original order. Assume no word is empty.

func groupByFirstLetter(_ words: [String]) -> [Character: [String]] {
    // your code here
}
Show solution
func groupByFirstLetter(_ words: [String]) -> [Character: [String]] {
    var groups: [Character: [String]] = [:]
    for word in words {
        groups[word.first!, default: []].append(word)
    }
    return groups
}

let groups = groupByFirstLetter(["apple", "banana", "avocado", "blueberry", "cherry"])
assert(groups["a"] == ["apple", "avocado"])
assert(groups["b"] == ["banana", "blueberry"])
assert(groups["c"] == ["cherry"])
assert(groups.count == 3)

The default can be any value of the right type, here an empty array, and append modifies the value stored in the dictionary directly. The standard library also offers this as Dictionary(grouping: words) { $0.first! }.

What’s next

Next lesson: optionals, the type behind every ? and ! so far.