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
- Swift book
- Collection Types
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"])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)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)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.
for letter in text {print(letterCounts("banana")["a"]!)}func letterCounts(_ text: String) -> [Character: Int] {}counts[letter, default: 0] += 1var counts: [Character: Int] = [:]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.