A Dictionary is an unordered collection of mappings/associations from keys to values. It is very similar to a Set and has the same performance, but stores a key/value pair, and only the key has to be Hashable. It can be used for storing preferences, or when you have a group of named values where there are either too many or change too often to be hardcoded. Then, you can use the names as keys:

Dictionaries

The full name is Dictionary<Key, Value>, but it is more commonly written as [Key: Value].

Dictionary ignores the order in which values are added or removed, and may change them arbitrarily, just like Set.

A CountedSet allows you to add equal elements more than once, and keeps count of how many of each element it contains. Naturally, it is very useful for counting things, such as how many times a word appears in a text, without having to store each word more than once.

To use an Xcode playground to develop a new CountedSet type using a dictionary internally.

  1. Open the CollectionsExtra Xcode project we used earlier, and go to CountedSet.swift.
  2. Leave the commented-out code as is, and add this to the top of the file:
    public struct CountedSet<Element: Hashable> {
      typealias ElementsDictionary = [Element: Int]
      private var elements: ElementsDictionary
    
      public init() {
        elements = ElementsDictionary()
      }
    }

    We use a type alias here because ElementsDictionary will be referred to several times in the code.

  3. Add the following code below the initialiser:
    public mutating func insert(_ newelement: Element, count: Int = 1) {
      elements[newelement, default: 0] += count
    }

    When inserting, we first get the current count of the element (or 0 if the element is not in the dictionary), then we add how many times the element should be inserted (1 by default) to this and insert the new value into the dictionary. += here means this:

      elements[newelement] = elements[newelement, default: 0] + count
  4. Now, we implement adding a Sequence of elements to the set:
    public mutating func insert<S>(contentsOf other: S) where S:Sequence, S.Element == Element {
      for newelement in other {
        insert(newelement)
      }
    }

    The generic <S> combined with the where clause allows us to use any sequence here, as long as its elements are the same type as the elements of this set.

  5. We also need a way to query how many of an element this set contains:
    public func count(for element: Element) -> Int {
      return elements[element, default: 0]
    }

    If the elements dictionary does not contain the element, we return 0 instead.

  6. And here is the method for counting the total number of elements:
    public var count: Int {
      var result = 0
      for count in elements.values {
        result += count
      }
      return result
    }
  7. It's time to verify whether this is working or not. Go to CountedSetTests.swift, uncomment the testInsert unit test, and run it.
  8. Go back to CountedSet.swift.
  9. Now, we can add some helpful initialisers. Add the following code below the first initialiser:
    public init<S>(_ other: S) where S:Sequence, S.Element == Element {
      self.init()
      insert(contentsOf: other)
    }

    This allows us to initialise from a sequence:

    CountedSet(["a","b","c","a"])
  10. Add the following code below the entire struct declaration:
    extension CountedSet: ExpressibleByArrayLiteral {
      public init(arrayLiteral elementarray: Element...) {
        self.init(elementarray)
      }
    }

    Now, if a function asks for a CountedSet, we can use an array literal directly.

  11. Within the struct declaration, right below the last count method, insert the following code:
    @discardableResult
    public mutating func remove(_ element: Element, count countToRemove: Int = 1) -> Int {
      guard var count = elements[element] else { return 0 }
      count -= countToRemove
      guard count > 0 else {
        elements.removeValue(forKey: element)
        return 0
      }
      elements[element] = count
      return count
    }

    This is the most complex code we have used so far. It lowers the count of the element by the provided amount, and returns the new count. Here's the explanation:

  12. At the bottom of the file, there is code for adopting the Collection protocol. Uncomment it. It is too long to go through in detail here, but feel free to look through it.
  13. Also uncomment all the unit tests in CountedSetTests.swift, and verify whether they all pass or not. Notice how the unit tests use methods such as contains and isEmpty that we did not implement, but got for free because we adopted the Collection protocol.
  14. The contains method from the collection protocol is quite inefficient for our type, because it goes through every single element and compares it to the element it is searching for. We can do better. Add the following code below the remove method in the struct declaration:
    public func contains(_ element: Element) -> Bool {
      return elements[element] != nil
    }

    This checks the dictionary directly, which as we mentioned earlier is much faster.