Start learning free

LESSON 260 OF 415 · HASH TABLE

Hash Maps in Swift: Dictionary Basics

Lesson Read the full explanation

Hash Tables, better known as Hash Maps, are just collections of key-value pairs. In other words, they are pieces of data (values) mapped to unique identifiers called properties (keys).

Hash Maps were designed to give us a way of, given a key, associating a value with it for very quick lookups.

In Swift, the Hash Map is implemented as a Dictionary


Creation:

You create an empty Hash Map by declaring a [keyType: valueType] dictionary, where the keyType and valueType are the data types of the keys and values.

Let's declare a Hash Map that maps text values representing digits to their numerical value (e.g. "two" => 2). We will map strings to integers, so the two types are String and Int. We will name the map digitValue

var digitValue = [String: Int]()

Creating an empty dictionary takes O(1) time.


Initialization:

You can also add some key-value pairs in your dictionary at creation:

var digitValue: [String: Int] = [
    "zero": 0,
    "one": 1
]

Accessing values:

A value is retrieved from a map using the subscript [] operator:

var digitValue: [String: Int] = [
    "zero": 0,
    "one": 1
]

// Accessing values:
digitValue["zero"] // returns Optional(0), an optional holding 0

digitValue["one"] // returns Optional(1)

digitValue["three"] // returns nil since 'three' does not exist

digitValue["three", default: 0] // returns 0 as the default value

As you can see, if you refer to a key that is not in the map, Swift returns nil. This is why a subscript read gives an optional value. We can use ?? defaultValue or [key, default: value] to specify a default value in case it does not exist

Accessing a value from a map takes O(1) time.


Adding an entry:

We can add a key / value pair by using the subscript assignment as follows:

var digitValue: [String: Int] = [
    "zero": 0,
    "one": 1
]

// Adding new entries:
digitValue["three"] = 3

//The new Hash Map:
// [
//  "zero": 0,
//  "one":  1,
//  "three": 3
// ]

Adding a new entry to a map takes O(1) time.


Updating an entry:

We can update an existing entry by using the subscript assignment as follows:

var digitValue: [String: Int] = [
    "zero": 0,
    "one": 1
]

// updating entries:
digitValue["zero"] = 5

//the new hash map:
// [
//  "zero": 5,
//  "one":  1
// ]

Updating an entry from a map takes O(1) time.


Deleting entries:

The removeValue(forKey:) method allows you to remove a key from a map:

var digitValue: [String: Int] = [
    "zero": 0,
    "one": 1
]

// removing entries:
digitValue.removeValue(forKey: "zero")

//the new hash map:
// [
//  "one":  1
// ]

If you try to delete a key that is not in the map, it won't do anything.

Deleting an entry from a map takes O(1) time.


Checking if a key exists:

You can check if a key exists in a map using the keys.contains() method:

var digitValue: [String: Int] = [
    "zero": 0,
    "one": 1
]


digitValue.keys.contains("zero") // returns true since the key exists

digitValue.keys.contains("three") // returns false since the key does not exist

Checking if a key exists in the map is O(1) time.


Iterating over the map keys:

If we want to iterate over keys of the map, we can use the for loop along with the in keyword and the keys property as follows:

var digitValue: [String: Int] = [
    "zero": 0,
    "one": 1
]

for key in digitValue.keys {
    print("\(key) is \(digitValue[key]!)")
}

// This prints both lines, in an unspecified order, for example:
// one is 1
// zero is 0

Notice the order is not the same as initiated. Dictionary keeps the data in no particular order, and the order can differ between runs

Iterating over a dictionary takes O(n) time.



Assignment
Follow the Coding Tutorial and let's play with some dictionaries.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 7
    Ctrl + Enter to check

    SWIFT · HASH TABLE

    Hash Map in Swift, explained

    What a hash map is

    A hash map stores key-value pairs. You give it a key and it hands back the value attached to that key, without scanning through everything it holds. Lookups, insertions and removals take O(1) time on average. Use one whenever you need to answer questions such as "how many times has this word appeared?" or "have I seen this number before?".

    In Swift, the hash map is the Dictionary type. A dictionary holding strings as keys and integers as values has the type [String: Int]. The keys must be hashable (strings, integers and characters all are), and each key appears at most once.

    Creating a dictionary

    An empty dictionary needs its types spelled out, because Swift can't guess them:

    var digitValue = [String: Int]()
    var other: [String: Int] = [:]
    

    With initial contents, write a literal. Swift infers the type from it, but you may state the type too:

    var digitValue: [String: Int] = [
        "zero": 0,
        "one": 1
    ]
    

    Use var for a dictionary you will change. A dictionary declared with let can't be modified.

    Reading values

    Subscripting with a key returns an optional, because the key might be missing:

    digitValue["zero"]   // Optional(0)
    digitValue["three"]  // nil
    

    You can't use that result as an Int directly. There are three common ways to deal with it:

    • digitValue["three"] ?? 0 gives 0 when the key is missing, and the stored value otherwise.
    • digitValue["three", default: 0] does the same, with the default inside the subscript.
    • if let value = digitValue["one"] { ... } runs the block only when the key exists.

    Reading with a default doesn't add the key. After digitValue["three", default: 0], the dictionary still has two entries. The default form also works for updating, so counting is short:

    var counts = [String: Int]()
    for w in ["a", "b", "a"] {
        counts[w, default: 0] += 1
    }
    // counts["a"] is 2 and counts["b"] is 1
    

    Adding, updating and removing

    Assigning through the subscript adds the key if it is new and replaces the value if it exists:

    digitValue["three"] = 3   // adds
    digitValue["zero"] = 5    // replaces
    

    To remove an entry, call removeValue(forKey:). It returns the removed value as an optional, nil if the key wasn't there, and removing a missing key changes nothing. Assigning nil through the subscript, as in digitValue["one"] = nil, removes the entry too.

    Checking and iterating

    To test whether a key exists, use digitValue.keys.contains("zero"), or compare the subscript with nil. Both are O(1).

    To visit every entry, loop over keys, or over the pairs themselves:

    for key in digitValue.keys {
        print("\(key) is \(digitValue[key]!)")
    }
    for (key, value) in digitValue {
        print(key, value)
    }
    

    Iteration takes O(n) time. A Dictionary has no guaranteed order, and the order may differ from one run of the program to the next. If you need a stable order, sort the keys first: for key in digitValue.keys.sorted().

    The exercise

    The lesson asks you to manage a stock of fruit, one step at a time:

    1. Declare fruits as a [String: Int] with "apples": 3, "bananas": 5 and "grapes": 2.
    2. Add 4 apples. The current count is optional, so ?? 0 supplies a number to add to.
    3. Print Out of pears when the "pears" key is missing, by testing the subscript against nil.
    4. Remove "bananas" with removeValue(forKey:).
    5. Loop over fruits.keys and print each count. The key comes from the dictionary, so fruits[fruit]! can't fail.
    var fruits: [String: Int] = [
        "apples": 3,
        "bananas": 5,
        "grapes": 2
    ]
    
    fruits["apples"] = (fruits["apples"] ?? 0) + 4
    
    if fruits["pears"] == nil {
        print("Out of pears")
    }
    
    fruits.removeValue(forKey: "bananas")
    
    for fruit in fruits.keys {
        print("We have \(fruits[fruit]!) \(fruit).")
    }
    

    The program prints Out of pears, then We have 7 apples. and We have 2 grapes.. The two We have lines can come out in either order, because the dictionary has no fixed order. The lesson accepts both.

    Gotchas

    • Forgetting the optional. fruits["apples"] + 4 doesn't compile, because the left side is an Int?. Unwrap it with ?? or if let.
    • Force-unwrapping a missing key. fruits["pears"]! crashes the program. Use ! only when you know the key is present, as in the loop above.
    • Relying on order. Don't expect entries to come back in the order you wrote them.
    • Using let. A constant dictionary can't be changed.
    • Copies. Dictionaries are value types. Assigning one to another variable makes an independent copy.

    Key takeaways

    • A Swift hash map is a Dictionary, written [Key: Value].
    • A subscript read returns an optional. Use ??, default: or if let.
    • Assign through the subscript to add or update, and use removeValue(forKey:) to delete.
    • Lookup, insert and remove are O(1) on average, and iteration is O(n) in no particular order.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free