Dictionary in Swift

In this doc we will talk about something that we need pay attention to when we are dealing with dictionaries in programming.

Convenient Methods

1. Dictionary Initialization From Sequence

In Swift, you can initialize a dictionary from a sequence of key-value pairs. The sequence can be an array of tuples or a dictionary.

1
2
let arr = [(1, "a"), (2, "b"), (3, "c")]
let dict = Dictionary(uniqueKeysWithValues: arr)

It’s helpful to convert from an array to a dictionary of the [element: index] format.

1
2
let arr = ["a", "b", "c"]
let dict = Dictionary(uniqueKeysWithValues: arr.enumerated().map { ($1, $0) })

Easy to Make Mistakes

1. Handle with Default Value

When you are dealing with a dictionary, you may want to get the value of a key, and if the key does not exist, you want to return a default value. You can use the default parameter of the subscript method.

1
2
3
var ints = [("a", 1), ("b", 2), ("c", 3), ("a", 2)]
var counter: [String: Int] = [:]
ints.forEach { counter[$0.0, default: 0] += $0.1 }

This works because the default parameter of the subscript method will return the default value if the key does not exist.
What if the value type is a custom type? Can you still use the default parameter? No, you can’t. You need to use the nil-coalescing operator to provide a default value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Person {
var name: String
var age: Int
}

var persons = [("a", Person(name: "Alice", age: 20)), ("b", Person(name: "Bob", age: 30), ("a", Person(name: "Alice", age: 25))]
// This will not work:
var counter: [String: Person] = [:]
persons.forEach { counter[$0.0, default: Person(name: "", age: 0)].age += $0.1.age }
// You need to use the nil-coalescing operator to provide a default value:
persons.forEach {
counter[$0.0] = (counter[$0.0] ?? Person(name: "", age: 0))
counter[$0.0]!.age += $0.1.age
}

Or, we can implement an extension for the dictionary to conform to the subscript: default pattern.

extension Dictionary {
    subscript(key: Key, default defaultValue: @autoclosure () -> Value) -> Value {
        mutating get {
            if let value = self[key] {
                return value
            } else {
                let value = defaultValue()
                self[key] = value
                return value
            }
        }
    }
}

> Why do we need to use `@autoclosure` here?
> The `@autoclosure` attribute automatically creates a closure around the expression. This is useful when you want to delay the evaluation of the expression until it is needed. In this case, we want to delay the creation of the default value until it is needed to avoid unnecessary computation.