Swift for Rubyists: Dictionaries

This post is the second in a series I am doing on Swift for Rubyists.

Forgive me for the lines that wrap on this one. I did the best I could to try and format them on one line, but all of the examples from this post are available as a playground to download at this repo.

Update: Apparently, my changes to fix the “<” & “>” symbols missing in the examples didn’t save. Fixed now.

Swift Dictionaries

Swift Dictionaries are equal to Hashes in Ruby. Like Swift Arrays, Swift dictionaries are like dictionaries/hashes in most modern programming languages including Ruby. Although Ruby hashes and Swift dictionaries share many similarities, there are some areas where their behavior is very different.

Swift Dictionary Declaration and Instantiation

Declaring a Ruby Hash, like an array, is very simple.

{}

Just like arrays, there are three different ways to declare a Swift Dictionary. In the first two ways, you must declare the type of keys and values before you instantiate the dictionary:

var swift_dict: Dictionary
var swift_dict_again: [String: String]

It is important to note that when declaring a Swift Dictionary in these first two ways, you must declare a type for both the key and value. In Ruby, any object can be a Hash key, but you have probably most often seen them as symbols or strings like the following examples:

{:key => "door"}
{"key" => "door"}

In order to be used as keys for a Hash (make it “hashable”), Ruby requires objects to implement “#eql?” and “#hash” methods. Strings and symbols both do these by default in Ruby.

Swift dictionary key types are the same way. Swift requires dictionary types to implement the Hashable protocol, which requires two methods “#hashValue” and the “==” operator. Strings, Int, Bool are all hashable types by default in Swift.

So, if you ever felt adventurous, you could create your own Swift type that implements the Hashable protocol that you could use as a Swift dictionary key. That technique is beyond the scope of this post.

Swift Dictionary Literals

There are Swift Dictionary literals. A Swift dictionary literal is very similar to Ruby’s hash literal. You are probably pretty familiar with this as it is found all throughout Ruby web frameworks like Rails:

{:key => "value"}
//or
{key: "value"}

The syntax for a creating a Swift dictionary literal is slightly different, but very important. We lose the curly brackets and signature “fat arrow”, move the colon (depending on which Ruby syntax you choose) and add square brackets.

In Swift, curly brackets, {}, are very important. Swift uses curly brackets to define functions and closures. Swift uses square brackets for dictionaries. Here’s a Swift Dictionary literal:

var dictionary = ["key":"value"]

Like Swift array literals, we do not have to declare a type for the keys and values of the dictionary because Swift uses type inference to infer the kinds types for the keys and values. In this case, and

However, as I pointed out in my post on Swift Arrays for Rubyists, you must use literals in Swift carefully.

For example, let’s say you wanted to set the swift dictionary you created above to an empty dictionary. You would do the following:

var refrigerator = ["milk":"1%"]
refrigerator = [:]
println(refrigerator.isEmpty) //true

You might think that “[:]” is just the literal way of creating an empty dictionary. It is not. If you have imported Objective-C’s Foundation library, it will actually compile, but it creates an NSDictionary, not a Swift Dictionary. Here’s an example:

import Foundation

var not_a_swift_dict = [:]
print("\(_stdlib_getDemangledTypeName(not_a_swift_dict))")  //"NSDictionary"

If you want to create an empty Swift Dictionary, the proper way to do it is:

let swift_dic = [<SomeType>: <SomeType>]()

Remember from Swift arrays that declaring and instantiating a dictionary in Swift are two different processes. Unless you are using a literal, you have to declare a Swift dictionary and then instantiate it.

For example, if we declare a dictionary in Swift, but do not instantiate it, properties like “count” will not exist and will cause a compile error.

var not_instantiated_dictionary = [String: String]
not_instantiated_dictionary.count //error

Modifying a Dictionary

Obviously, hashes and dictionaries are not very useful if you can’t add/remove items to them. Luckily, Swift uses subscript syntax for adding new objects to a dictionary which is the same style of syntax as Ruby’s hash.

hash[<SomeObject>] = <SomeObject>

Here are two examples side-by-side both using Strings as the key and value type side-by-side:

ruby_refrigerator = {}
ruby_refrigerator["milk"] = "2%"

var swift_refrigerator = [String: String]()
swift_refrigerator["milk"] = "2%"

It is important to note that we are declaring the Swift dictionary as a variable. We haven’t covered the difference between constants and variables in Swift, but know that constants in Swift are immutable. In this case, that means we cannot add or remove items from them. So, we are declaring it as a variable.

You can use the Swift Dictionary method “#updateValue” instead of the subscript syntax. However, this method has an interesting side-effect. “#updateValue” will always return a Swift optional of the old value that it is updating.

We haven’t touched Optionals yet in Swift, but they are a big and complex part of Swift that we will go further in-depth on another blog post.

For now, the most important thing to remember about Swift Optionals is that they are a type that is either some value of that type or nil.

Why is this important to “#updateValue”? Well, let’s look at our milk example above. What if we substituted Swift’s subscript syntax for the updateValue method? Would it still work? Absolutely.

var swift_refrigerator = [String: String]()
swift_refrigerator.updateValue("1%", forKey: "milk") 

“#updateValue” will actually set a value for a key if no value exists. This is similar to Ruby Hash’s “#store” method.

However, “#store” and “#updateValue” return different values. Ruby’s “#store” will return the value that was just assigned. Swift’s “#updateValue” will return the original value for the key.

But what about our example? There was no original value for milk. So, shouldn’t “#updateValue” return nil? In many cases, Swift will return an Optional where nil is a possible return value.

In this case, Swift returns a String optional (written, String?). Here’s proof:

var swift_refrigerator2 = [String: String]()
let food = swift_refrigerator2.updateValue("2%", forKey: "milk")
print("\(_stdlib_getDemangledTypeName(food))") 
//Swift.Optional

We don’t have to specify that “food” is a String optional, because Swift is using type inference to decide the type of “food”. It already knows the return type of “#updateValue”, so we don’t have to specify it. Type inference is a powerful, but complex, feature of Swift that gives the compiled language a Ruby-like feel at times.

So, we could re-write our example above as:

var milk_optional : String? = swift_refrigerator2.updateValue("%2", forKey: "milk")

Removing a key and its value from a Swift Dictionary with “#removeValueForKey” returns a String optional as well and operates largely the same way.

var depleting_refrigerator = ["milk":"2%"]
let milkless = depleting_refrigerator.removeValueForKey("milk")
print("\(_stdlib_getDemangledTypeName(milkless))") 
//Swift.Optional

In the case that no key exists, Swift will still return an optional.

var eggless_refrigerator = ["milk":"2%"]
let hungry = eggless_refrigerator.removeValueForKey("eggs")
print("\(_stdlib_getDemangledTypeName(hungry))") 
//Swift.Optional

Iterating over a Swift Dictionary

Just like Ruby Arrays and Swift Arrays, Ruby Hashes and Swift Dictionaries are very similar in how you iterate over them.

Each data structure can use for..in loop for iteration. In Ruby:

best_pictures = {1973 => "The Godfather", 
                 1989 => "Rain Man", 
                 1995 => "Forrest Gump"}

for year, title in best_pictures
  puts "The award for Best Picture in #{year}: #{title}"
end
//Outputs
//The award for Best Picture in 1973: The Godfather
//The award for Best Picture in 1989: Rain Man
//The award for Best Picture in 1995: Forrest Gump

In Swift, while the syntax is similar, a Swift for..in loop over a dictionary returns a tuple, a Swift type that does not exist in Ruby. It essentially is a single type that can contain multiple types (a dream within a dream? I know. More on this later).

In this case, this allows the Swift for..in loop to behave very similarly to the Ruby for..in loop.

let best_pictures = [1973:"The Godfather", 
                     1989:"Rain Man", 
                     1995:"Forrest Gump"]

for (year, title) in best_pictures {
  println("The award for Best Picture in \(year): \(title)")
}

//Outputs
//The award for Best Picture in 1973: The Godfather
//The award for Best Picture in 1989: Rain Man
//The award for Best Picture in 1995: Forrest Gump

Swift .Keys and .Values

Just like Ruby, Swift provides a couple of convenient ways for getting only the keys and values of a dictionary.

In Ruby, “#keys” and “#values” are methods that both returns arrays of their respective items, like so:

roster = {:pitcher => "Justin Verlander", 
          :first_base => "Miguel Cabrera" }
positions = roster.keys //[:pitcher, :first_base]
players = roster.values //["Justin Verlander", "Miguel Cabrera"]

In Swift, they are slightly different. In Swift, .Keys and .Values are properties of a dictionary and return not arrays, but LazyBidirectionalCollections (isn’t that a mouthful)? To convert a LazyBidirectionalCollection into a Swift Array, you pass the collection into the Swift array initializer function. For example:

let roster = ["pitcher": "Justin Verlander", 
              "first base": "Miguel Cabrera"]
let positions = [String](roster.keys) 
//["pitcher","first base"]

Note: there is another, undocumented (at least by Apple), way to convert the .keys or .values collection into an array. Why this is not documented or discussed in any way is beyond me. See below.

You can also use the convenient “array” property on each of these collections to get an array of their respective values.

let roster2 = ["pitcher": "Justin Verlander", 
               "first_base": "Miguel Cabrera"]
let positions2 = roster.keys.array

More to Come

While we covered a bit about Swift Dictionaries for Rubyists, this post has only been a brief introduction. If you have found this information useful and would like to learn more, please sign up for my Swift Newsletter below and I will send you updates when new posts become available. You’ll also receive access to my video tutorial “Advanced Swift Arrays”.

Learn more about other areas of Swift from a Rubyist’s perspective.

  • Kakubei

    Great series, please do keep going. However, there is an error, this line of code:


    let best_pictures = [1973:"The Godfather",
    1989:"Rain Man",
    1995:"Forrest Gump"]

    is a hash within an array and would not produce:

    //The award for Best Picture in 1973: The Godfather

    I think you meant to use curly braces {} instead of brackets [].