Swift Dictionary Field Notes

Dictionary Fundamentals

A Dictionary, provided by Foundation, is a type of hash table that stores key/value pairs.

  • Keys must conform to Hashable
  • Access to elements is O(1)
  • Derived from NSDictionary

Best used for key/value storage where lookup by key is the primary use case. However, Apple has added a lot of handy routines to Dictionary that make it useful for many types of data processing and analysis out of the box.

Create an Empty Dictionary

var empty:[Int:String] = [:]

Create using Type Inference

var population = [String: Int]()

Accessing the value of a key

Values are optional, because keys may or may not exist

var population = [String: Int]()

population["us"] = 350_000_000

print(population["us"])  // Optional(350000000)
print(population["fr"])  // nil

Check for Key Existence

var population = [String: Int]()

population["us"] = 350_000_000

print(population.keys.contains("us")) // true

Use a struct as the key

To use a struct as a key, declare the struct Hashable.  If all members of the struct already conform to Hashable, all properties will be used as the hash key. If not, follow the template for using a class as the key (below)

struct Country : Hashable {
   let name : String
   let iso : String
}

var country: [Country : Int] = [:]
    
let usa = Country(name: "United States", iso: "us")

country[usa] = 1
country[usa] = (country[usa] ?? 0) + 1  

print(country[usa] ?? 0)  // 2

Using a class as the key

Classes do not automatically synthesize Equatable or Hashable, so implement these conformances manually before using the class as a Dictionary key.

class Country {
    let name : String
    let iso : String

    init(name: String, iso: String) {
        self.name = name
        self.iso = iso
    }
}

extension Country : Equatable {
    static func == (lhs: Country, rhs: Country) -> Bool {
        return lhs.iso == rhs.iso
    }
}

extension Country : Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(iso)  // iso itself is a reasonable hash key
    }
}

var countries = [Country : Int]()

let usa = Country(name: "United States", iso: "us")

countries[usa] = 1
countries[usa] = (countries[usa] ?? 0) + 1

print(countries[usa] ?? 0)  // 2

Use a Dictionary to count the frequency of values in an array

Dictionary and functional programming can be used to create a frequency table in a single line of code.

  1. Use map over an array to create a tuple for each array element
  2. Use Dictionary's uniquingKeysWith to create a count of keys, storing the result in a dictionary
let friends = ["John", "Mary", "Sam", "John", "Adam", "John"]

let friendsNameCount = Dictionary(
                          friends.map { ($0, 1) }, 
                          uniquingKeysWith: +)

print(friendsNameCount) // ["John": 3, "Mary": 1, "Sam": 1, "Adam": 1]

print(friendsNameCount["John"]) // Optional(3)

Get a sorted list of keys

let friends = ["John", "Mary", "Sam", "John", "Adam", "John"]
let friendsNameCount = Dictionary(friends.map { ($0, 1) }, uniquingKeysWith: +)

print(friendsNameCount.keys.sorted(by: <))

// output
["Adam", "John", "Mary", "Sam"]

Iterate over dictionary contents in no specific order

let friends = ["John", "Mary", "Sam", "John", "Adam", "John"]

let friendsNameCount = Dictionary(friends.map { ($0, 1) }, uniquingKeysWith: +)

for (name, frequency) in friendsNameCount {
    print("\(frequency) friends are named \(name) ")
}

// output
3 friends are named John 
1 friends are named Sam 
1 friends are named Adam 
1 friends are named Mary 
c

Iterate over dictionary contents sorted by key #1

One way to access elements by sorted keys is to fetch the keys, sort the keys, and then use the keys to access the underlying objects.  

let friends = ["John", "Mary", "Sam", "John", "Adam", "John"]

let friendsNameCount = Dictionary(friends.map { ($0, 1) }, uniquingKeysWith: +)

let sortedNames = friendsNameCount.keys.sorted(by: <)

for name in sortedNames {
    print("\(friendsNameCount[name]!) friends are named \(name) ")
}

// output
1 friends are named Adam 
3 friends are named John 
1 friends are named Mary 
1 friends are named Sam 

Iterate over dictionary contents sorted by key #2

A more concise way to accomplish the previous example is to use the Dictionary sorted command to return an array of dictionary contents.

let friends = ["John", "Mary", "Sam",
               "John", "Adam", "John", "Mary"]
               
let friendsCount = Dictionary(
                     friends.map { ($0, 1) },
                     uniquingKeysWith: +)

let sortedByFrequency =
    friendsCount.sorted { $0.key < $1.key }

for (name, frequency) in sortedByFrequency {
    print("\(frequency) friends are named \(name)")
}

// output
1 friends are named Adam
3 friends are named John
2 friends are named Mary
1 friends are named Sam

Related functional commands available to Dictionary:

map, compactMap, mapValues, compactMapValues, reduce, flatMap, shuffled

Iterate over dictionary contents sorted by value

let friends = ["John", "Mary", "Sam",
               "John", "Adam", "John", "Mary"]
               
let friendsCount = Dictionary(
                     friends.map { ($0, 1) },
                     uniquingKeysWith: +)

let sortedByFrequency =
    friendsCount.sorted { $0.value > $1.value }

for (name, frequency) in sortedByFrequency {
    print("\(frequency) friends are named \(name)")
}

// output
3 friends are named John
2 friends are named Mary
1 friends are named Adam
1 friends are named Sam

Inspect Dictionary values for existince of combination of key/value

let friends = ["John", "Mary", "Sam", 
               "John", "Adam", "John", "Mary"]
               
let friendsCount = Dictionary(
                     friends.map { ($0, 1) }, 
                     uniquingKeysWith: +)

let isPresent = friendsCount.contains { 
                      $0.key == "John" && $0.value == 3 }

// isPresent = true

Related searching routines:

allSatisfy, first, firstIndex, min, max

Convert a Dictionary to a JSON string

It's possible to leverage the Dictionary encode function to return a dictionary's contents as a JSON string.  This may be of limited practical value, since the format of the JSON is limited, but can be handy in certain scenarios.

struct Friend : Codable,  CustomStringConvertible {
    let name: String
    let age: Int
    
    var description: String {
        return "\(name)"
    }
}

let friends = [
    "John" : Friend(name: "John", age: 21),
    "Mary" : Friend(name: "Mary", age: 31),
    "Josh" : Friend(name: "Josh", age: 21),
]


let encodedDict = try JSONEncoder().encode(friends)

// Output
{
    "Mary": {
        "name": "Mary",
        "age": 31
    },
    "Josh": {
        "name": "Josh",
        "age": 21
    },
    "John": {
        "name": "John",
        "age": 21
    }
}
Rob Kerr
App development for iOS, creating applications for my own development studio (Cuvenx Inc.), and consulting with awesome clients to build their mobile applications.
Ann Arbor, Michigan, USA

Copyright © 2021 Rob Kerr