Learning Swift: Ordered Dictionaries

Note: this post begins a new series about the Swift programming language, introduced at WWDC 2014. I’m no more experienced in Swift than anyone else outside Apple, but I learn best by coding and talking through a problem. If there’s a better way to approach some of these topics, get in touch on Twitter!

An ordered dictionary is a useful data structure that combines attributes of an array and a dictionary. Like an array, it stores elements in the order they’re added, and enumeration happens in the same fixed order every time. It also satisfies the basic idea of a dictionary: it stores key-value pairs, and can look up values by key.

Ordered dictionaries are incredibly useful tools for all kinds of development. To crib an example from the Swift book, an ordered dictionary might help an app display people and their ages: you might use names for keys, ages for values, and use the order to provide data in table cells by index path (and support user-driven reordering, to boot).

In this article, we’ll build an ordered dictionary atop the two primitive collection types already in Swift: an Array and a Dictionary. Let’s go!

The Shoulders of Giants

As described in The Swift Programming Language, Apple provides us some basic collection classes already. Most iOS developers are probably already familiar with the concepts of an array and a dictionary, but there are a few key caveats about the Swift equivalents.

First, they’re no longer prefixed. NSArray and NSDictionary served us well for many years, but have been replaced in the Swift world. The equivalents, Array and Dictionary, are fundamental types in the language, bringing along with them a few new behaviors.

The most notable of these is increased type safety. Swift, unlike Objective-C, supports type generics: a way of stating that a variable’s type or types can be determined later without throwing away all type information about that variable. (This concept also goes by the alias parameterized types.)

For example, in Objective-C we were limited to declaring an NSArray of objects, then trusting that they were all NSStrings. By contrast, in Swift we can declare that a given Array only contains String instances. The full type of such a variable would be Array<String> (in a syntax that is probably familiar to former Java programmers). Swift also provides a shorthand for this type: String[]. Either is acceptable, and both are interchangeable.

Dictionary has similar semantics: when creating a new Dictionary instance, you’ll specify the type for both the key and the value. We write such a type as Dictionary<KeyType,ValueType>; our hypothetical name-to-age mapping as a Dictionary would thus have the full type Dictionary<String,Int>.

One final change we see in Swift collections is their memory semantics. In Objective-C, NSString and NSDictionary were classes, and had appropriate semantics: they were passed (effectively) by reference, and copied only when necessary. Swift’s Array and Dictionary are, by contrast, structs; this means that in most cases, they’re copied when modified, assigned to a variable, or passed to a function. (Array has some exceptions to this behavior: for performance, it doesn’t always copy on assignment or mutation, but can be forced to do so if necessary by calling unshare().)

Beginning an Implementation

Since we desire most of the same memory behavior for our hypothetical ordered dictionary, let’s begin implementing it as a struct:

struct OrderedDictionary {}

We’d like to specify that this dictionary always maps keys of a certain type to values of a certain (potentially different) type, so let’s add some generic types to its declaration:

struct OrderedDictionary<Tk,Tv> {}

Here, Tk stands in for the type of the dictionary’s keys, and Tv for the type of the values. Next, we’ll need a way to store the keys and values they map to; let’s provide an array for the former, and a dictionary for the latter.

struct OrderedDictionary<Tk,Tv> {
    var keys: Array<Tk>
    var values: Dictionary<Tk,Tv>
}

Whoa! We’ve hit our first compile error:

Type ‘Tk’ does not conform to protocol ‘Hashable’

That’s right - Swift dictionaries need to be able to hash their keys. The language provides a Hashable protocol for that, which in turn defines a single method: hashValue(). Let’s constrain our Tk generic type so that we’re sure it conforms to this protocol:

struct OrderedDictionary<TK: Hashable, Tv> { /* ... */ }

Just like declaring class inheritance or protocol conformance, we can have a generic type include a constraint by naming the required protocol after a colon. It’s almost as if we’re declaring the “type of the type,” so to speak.

Believe it or not, this is actually coming close to a complete ordered dictionary type definition. This little snippet will compile and is usable, thanks largely to Swift’s various bits of inference and automatic code generation. Let’s gin up a little command-line tool that makes one of these things and prints it out:

let od = OrderedDictionary(keys: ["Tim"], values: ["Tim" : 24])
println(od)

If we run that code (with the appropriate imports, if needed), we’d see the following output:

V19SwiftDataStructures17OrderedDictionary (has 2 children)

Hm. That seems remarkably unhelpful in a couple different ways:

  • We don’t really get any useful information about the dictionary, and
  • We have to specify its contents in a fairly roundabout way, naming the key twice (potentially introducing an inconsistency along the way)

Better Behaved

Any developer who tries to use our OrderedDictionary is first going to need to create an instance, so let’s focus some efforts there. As shown in the snippet above, Swift has automatically created a memberwise initializer for this struct, so that callers can make an instance by providing a value for every member. This isn’t ideal – it requires that callers specify keys twice, for example.

Let’s provide our own initializer on the struct. For now, we don’t want to take any initial keys or values in (we’ll provide that later); we just want to set up an empty OrderedDictionary instance for the caller.

struct OrderedDictionary<Tk: Hashable, Tv> {
    var keys: Array<Tk> = []
    var values: Dictionary<Tk,Tv> = [:]

    init() {}
}

This initializer is about as bare as it gets: no arguments and no body. It accomplishes its purpose, however, in that it prevents Swift from generating a memberwise initializer for this struct – the language will refuse to do so if it detects that the developer has provided any custom initializers.

At the same time, though, we’ve made another slight tweak here to ensure that our struct is still fully initialized by the time the init() method returns: we’ve added default values for both of our var declarations. Since a new OrderedDictionary has no contents, we’ve set up the keys variable to have an empty Array, and given the values variable a similarly empty Dictionary.

With these changes, our stub code to make and print an instance looks a little cleaner:

let od = OrderedDictionary()
println(od)

Now, we can’t add any values to this dictionary up front, but at least we’re not repeating ourselves. Let’s bring back that mutability.

Subscripting

Swift brings over a handy language feature from Objective-C: the ability to define a method that supports subscripting syntax. In Objective-C, we implemented the method -objectAtIndexedSubscript: to support array-style integer indexing, and -objectForKeyedSubscript: for dictionary-style key-value access.

Swift combines both of these methods into a single keyword subscript, which infers its behavior (integer-based or key-based) from the argument types with which it is implemented. For example, implementing subscript and typing its argument as an Int gets you array-like behavior, while typing the argument with an arbitrary key type will look more like a dictionary.

Let’s implement the basic dictionary subscripting on OrderedDictionary now:

struct OrderedDictionary<Tk: Hashable, Tv> {
    /* ... vars and init ... */

    subscript(key: Tk) -> Tv? {
        get {
            return self.values[key]
        }
        set(newValue) {
            if newValue == nil {
                self.values.removeValueForKey(key)
                self.keys.filter {$0 != key}
                return
            }
            
            let oldValue = self.values.updateValue(newValue!, forKey: key)
            if oldValue == nil {
                self.keys.append(key)
            }
        }
    }
}

That’s a lot of code all at once! Let’s go through it in a more fine-grained way:

subscript(key: Tk) -> Tv? {

This says we’re providing a subscripting implementation that takes our key type (Tk) as the subscript itself and returns something that is an optional value type (Tv?). Remember that it’s possible for us to return nil from subscript implementations – if we’re given a key that doesn’t exist, we can’t return a valid value, so we return nil and the return type has to be optional.

get {
    return self.values[key]
}

This is fairly straightforward: when getting the value for a given subscript, just ask the values dictionary for its own value for that key and return it unchanged.

set(newValue) {

This will be our implementation for a subscript setter; newValue is of the return type above, so Tv?. We’ll need to handle possible nil new values in this setter, so:

if newValue == nil {
    self.values.removeValueForKey(key)
    self.keys.filter {$0 != key}
    return
}

We’ll assume that setting a nil new value means we want to remove the key and associated value, so we remove the value from our values dictionary and the key from our keys array. The latter has to be accomplished by way of a filter for equality against the given key; since our key type is Hashable, we know it’s also Equatable, so we can use the equality operator != on instances.

let oldValue = self.values.updateValue(newValue!, forKey: key)
if oldValue == nil {
    self.keys.append(key)
}

On the other hand, if the new value isn’t nil, let’s insert it into our values dictionary (unboxing it from its optional type along the way). We also hang on to any value it’s replacing: if there wasn’t already a value for this key, we’ll need to tack the key onto the keys array as well, so as to maintain the right ordering.

Now, our stub runner code can not only make a new OrderedDictionary, but it can also insert some new values into it. Let’s keep on tracking names and ages using the same data from above, but this time set it using subscripting:

let od = OrderedDictionary()
od["Tim"] = 24
println(od)

Descriptivism

Now that we can shove values into an OrderedDictionary, let’s get them back out again. Much like Objective-C, we can implement a method description() that formats our instance to a nicer String:

var description: String {
    var result = "{\n"
    for i in 0..self.keys.count {
        let key = self.keys[i]
        result += "[\(i)]: \(key) => \(self[key])\n"
    }
    result += "}"
    return result
}

This implementation should be fairly straightforward, with the exception of a couple tiny new bits of syntax:

  • The declaration (var description: String { ...) is a read-only computed property. It doesn’t include “storage” (what we would think of as an instance variable), but instead provides a value based solely on other internal state.
  • The loop (for i in 0..self.count {) uses a range, which isn’t inclusive of its last index – we iterate up to, but not past, the count.

Now, our runner code can print out the description of our OrderedDictionary:

let od = OrderedDictionary()
od["Tim"] = 24
println(od.description)

And we should see the final output:

{
[0]: "Tim" => 24 
}

Voila! We have a working ordered dictionary struct with basic mutability and a handy description. A more complete implementation is available on GitHub.