Dictionaries

Note to the reader: you must understand optionals to fully understand this section; however, if you never use get-operations and only use it as an association list, you can get around this with for loops.

Dictionaries, or hash maps, can be thought of as an array that pairs keys with values. We can think of arrays as dictionaries where the keys are integers (they're implemented differently though). We denote the type of this key-value pairing with <key type> : <value type. So, when we want to state the type of a dictionary, we write the key-value pairing enclosed in square brackets: [<key type> : value type].

Creation

The creation of empty dictionaries is pretty similar to lists. You can either do it by passing the empty dictionary to an explicitly type variable:

var myDict: [String: Int] = [:]
// myDict: [String: Int] = {}

The other method is to act like the type is instantiable:

var myDict = [String: Int]()
// myDict: [String: Int] = {}

I will be using python dictionary notation / JSON to represent key-value pairs in my annotations:

{ key1: value1, key2: value2 ... }

We can also create a dictionary with pre-existing key-value pairs!

var nameLengths = [ "Noah" : 4, "Justin" : 6, "Amy" : 3 ]
// nameLengths: [String: Int] = {"Noah": 4, "Justin": 6, "Amy": 3}

Hashable

If you are working with a more complex key, you may come across the error "candidate requires that '<Insert your type>' conform to 'Hashable'". You don't really need to understand all of this–you'll get a better understanding when reading about protocols and conformance. For those that simply want to get your dictionary working I give you 3 solutions–although 1 and 2 are functionally the same it's just a question of organization:

  1. Conform your key type to hashable directly

struct MyKeyType: ..., Hashable... { ... }

2. Extend your key type to conform to hashable

extension MyKeyType: Hashable {}

3. Use a different key type

Note that for conformance to the Hashable protocol, you don't need to add any code unless you want behavior different from the default of comparing fields.

For those that are interested in what's going on here behind the scene... First off, 👏. Dictionaries are also called hash maps. Hash maps are an extremely powerful data structure because they guarantee great runtimes–the examination of which is beyond this course. If you're interested, you look up amortized runtime of insertion in hash maps. But broadly, hash maps leverage a hash function, which converts keys into integers to boost performance. In our case, the Hashable protocol provides hash functions for us! By conforming your key to the Hashable protocol, we can generate a hash function for our data type. How nice of the Swift devs :)

Basic Dictionary Operations

Get

Getting from a dictionary is similar to getting an element of an array! We do dictionary[key]. The difference from an array is that getting something from an element will always return an optional.

enum Courses: Hashable {
    case iOS
    case Backend
    case Design
}

var courseInstructors = [
    .iOS: ["Noah", "Justin"],
    .Backend: ["Kate", "Shungo"]
]
// courseInstructors: [Courses: [String]] = \
// {.iOS: ["Noah", "Justin"], .Backend: ["Kate", "Shungo"]}

let iOSInstructors = courseInstructors[.iOS]
// iOSInstructors: [String]? = Optional(["Noah", "Justin"],)

let designInstructors = courseInstructors[.Design]
// designInstructors: [String]? = nil

So, always be careful when getting from a dictionary! Of course, Swift will make you handle these errors accordingly.

Put

Putting key-value pairs into a dictionary is really easy! We just use the syntax dictionary[key]= value.

courseInstructors[.Design] = ["Tise", "Emily"]
// courseInstructors: [Courses: [String]] = {
//    .iOS: ["Noah", "Justin"],
//    .Backend: ["Kate", "Shungo"]
//    .Design: ["Tise", "Emily"]
//}

Remove

There's two good ways to remove key-value pairs. The first, is pretty dang easy. Just assign nil: dictonary[key] = nil.

The other is a method removeValue(forKey: Key).

courseInstructors[.iOS] = nil
courseInstructors.removeValue(forKey: .iOS)
// courseInstructors: [Courses: [String]] = {
//    .Backend: ["Kate", "Shungo"]
//    .Design: ["Tise", "Emily"]
//}

But let's imagine that never happened because we, the iOS course instructors, are vain and would like to be included!

Iteration

Iteration is where dictionaries start getting a tiny bit scary–but just a tiny bit. You will often find yourself trying to loop through a dictionary–i.e. do something for every key-value pair. If you try looping through one like an array, you will see one scary generic:

for course in courseInstructors { 
// course: Dictionary<Courses, [String]>.Element
}

So far, we have been avoiding as much of the scary parts of the implementations of dictionaries. And that won't change here (of course we will have an explanation at the end for those who are curious).

You can deal with this in two ways. The first choice is just to keep it as a generic. We can access the key of the pair through the key field and we can access the value through the value property:

for course in courseInstructors { 
// course: Dictionary<Courses, [String]>.Element = .iOS : ["Noah", "Justin"]
    let course = course.key
    // course: Courses = .iOS
    let instructors = course.value
    // instructors: ["Noah", "Justin"]
}

The other solution is what I prefer, but it's just a stylistic matter. Just like enumerations, dictionary elements (key, value pairs) support tuple assignment. This means we can do something sleek like:

for course, instructors in courseInstructors {
// course: Courses = .iOS
// instructors: [String] = ["Noah", "Justin"]
}

Also, remember first from arrays? Yeah, it's back if you want it. You can also sort elements.

Properties

There's really only one property that you will use with dictionaries, and that is count. count is exactly like its array counterpart: the number of key-value pairs currently stored in the dictionary.

courseInstructors.count
// 3: Int
// Remember, we added .Design

If y'all are doing some very fancy optimization programming, you may want to know about the load factor and capacity in a dictionary. First off, 👏 you are clearly doing well and loving Swift. Anyways, you may be interested in the capacity field. This get-only field is described as

The total number of key-value pairs that the dictionary can contain without allocating new storage.

in Swift documentation.

Last updated