Skip to content
Lesson 11 of 15

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:

FormResult when the key is missing
ages["Zoe", default: 0]0 — a plain Int, no optional
ages["Zoe"] ?? 00 — 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 firstfor 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 needUse
Order, duplicates, positionArray
Membership and de-duplicationSet
A value looked up by a keyDictionary

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.

Up nextHigher-Order MethodsCollections

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…