Step 1 of 5 · Reading · ~4 min
Learn
Collections
Dictionaries and Sets
Arrays answer "what is at position 3?". These two answer different questions: a dictionary
answers "what is stored under this key?", and a set answers "have I seen this before?".
Both are hash-based, both are unordered, and both require their keys to be Hashable — which
every basic Swift type already is.
Dictionaries
The type is written [Key: Value]:
var ages: [String: Int] = [
"Alice": 30,
"Bob": 25,
]
ages["Carol"] = 40
ages["Bob"] = 26
print(ages.count)
print(ages["Alice"] ?? -1)
//> 3
//> 30
Assigning to a key that does not exist inserts it; assigning to one that does replaces the value.
Lookup gives you an optional, always
This is the fact the whole type hangs on. ages["Alice"] has type Int?, not Int, because
Swift cannot know at compile time whether the key is there.
var ages = ["Alice": 30]
let found = ages["Alice"]
let missing = ages["Zoe"]
print(String(describing: found))
print(String(describing: missing))
//> Optional(30)
//> nil
Three ways to deal with that, in rough order of how often you want them:
| Form | Result when the key is missing |
|---|---|
ages["Zoe", default: 0] | 0 — a plain Int, no optional |
ages["Zoe"] ?? 0 | 0 — same, spelled with nil-coalescing |
if let a = ages["Zoe"] | the branch simply does not run |
var ages = ["Alice": 30]
print(ages["Zoe", default: 0])
print(ages["Alice", default: 0])
//> 0
//> 30
Note where the default: goes — inside the subscript, after the key. It is
ages["Zoe", default: 0], not a separate call.
The default: subscript has a second use that catches people out: it also works for writing,
which makes counting a one-liner — counts[word, default: 0] += 1 reads the value or zero, adds
one, and stores it back.
Removing, and detecting replacement
var ages = ["Alice": 30]
let previous = ages.updateValue(31, forKey: "Alice")
print(String(describing: previous), ages["Alice", default: -1])
ages.removeValue(forKey: "Alice")
print(ages.isEmpty)
//> Optional(30) 31
//> true
updateValue(_:forKey:) returns the old value — nil if there was none — which is how you
tell an insert from an overwrite. Setting a key to nil removes it, exactly like
removeValue(forKey:).
Iteration order is not defined
var ages = ["Alice": 30, "Bob": 25]
for (name, age) in ages {
print("\(name) is \(age)") // order can differ between runs
}
print(ages.keys.sorted())
//> ["Alice", "Bob"]
Only the last line is predictable. If a test compares your output line by line, sort the keys
first — for name in ages.keys.sorted() — or you will pass locally and fail on the grader for
no visible reason. (The OrderedDictionary type you may read about lives in the separate
swift-collections package and is not available here; sorting is the answer on this grader.)
Sets
A Set holds distinct values with no order and no duplicates. There is no shorthand for the
type, so you must name it:
let picked: Set<Int> = [3, 1, 2, 3]
print(picked.count)
print(picked.contains(2))
print(picked.sorted())
//> 3
//> true
//> [1, 2, 3]
The duplicate 3 simply is not stored. That single behaviour makes Set the shortest answer to
a whole class of questions:
let words = "the quick brown the".split(separator: " ").map(String.init)
print(words.count)
print(Set(words).count)
//> 4
//> 3
Set(words) builds a set from any sequence, discarding repeats; .count then tells you how many
distinct items there were.
Sets also do the algebra you would expect — union, intersection, subtracting,
symmetricDifference — and membership testing is O(1), against O(n) for an array's contains.
Which one?
| You need | Use |
|---|---|
| Order, duplicates, position | Array |
| Membership and de-duplication | Set |
| A value looked up by a key | Dictionary |
Your exercise
Read one line of space-separated words and print how many distinct words appeared.
The mistake the grader catches is counting the words instead of the distinct words. The first
visible test is the quick brown the — four words, three distinct. Printing words.count gives
4, which looks plausible right up until it fails. Wrap the array in a set first:
Set(words).count. The starter has already converted each Substring into a String for you,
which matters because a set of Substring and a set of String are different types.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…