Making a Custom Type Hashable

We have a custom Swift class and want to use it as the key of a dictionary. How do we do that?

Our custom Swift class represents a student with a unique studentID, a firstName and a lastName.

class Student {
    let studentID: Int
    var firstName: String
    var lastName: String
    
    init(studentID: Int, firstName: String, lastName: String) {
        self.studentID = studentID
        self.firstName = firstName
        self.lastName = lastName
    }
}

We have a track and field competition where a group of students competes in a 100-metre dash. We want to store the results of the races in a dictionary, where Student objects are mapped to the students’ 100-metre times. We want to write code like this:

    let larry = Student(studentID: 170523, firstName: "Larry", lastName: "Palmer")
    var results: [Student: Double] = [:]
    results[larry] = 10.75
    print("Larry's time was \(results[larry])")

We can use a custom class as the key type of dictionaries if the custom class implements the Hashable protocol. The Hashable protocol inherits from the Equatable protocol. Hence, we must implement both the Hashable and the Equatable protocol.

The Hashable protocol requires us to assign an integer value to the instance property hashValue. The property studentID is the ideal candidate, because it has type Int and it is unique for all students. The parts needed by the Hashable are shown in bold face.

class Student: Hashable {
    let studentID: Int
    var firstName: String
    var lastName: String
    var hashValue: Int {
        return self.studentID
    }
    
    init(studentID: Int, firstName: String, lastName: String) {
        self.studentID = studentID
        self.firstName = firstName
        self.lastName = lastName
    }
}

At this point, Xcode rightly complains that Type ‘Student’ does not conform to protocol ‘Equatable’. The Equatable protocol requires us to implement an equality operator for the Student class. We add the equality operator immediately after the definition of the Student class.

class Student: Hashable {
    // Implementation of Student as before ...
}


func ==(student1: Student, student2: Student) -> Bool {
    return student1.studentID == student2.studentID
}

It is important that the equality operator is defined outside the class Student in global scope. Swift defines all its equality operators in global scope and looks for custom equality operators there.

The Hashable protocol requires the equality operator to satisfy the following axiom. If two Student objects are equal, the hash values of these two objects must be equal as well.

    student1 == student2 implies student1.hashValue == student2.hashValue

This is certainly true for our equality operator. This axiom guarantees that there are not multiple entries for the same student in a dictionary. It also guarantees that two Studentobjects are different if their hash values are different.

The following unit test adds two students, Larry Palmer and Mike Miller, and their times in the 100-metre dash to the dictionary results. The unit test “proves” that Mike is faster than Larry.

    func testStudentInDictionary() {
        let larry = Student(studentID: 170523, firstName: "Larry", lastName: "Palmer")
        let mike = Student(studentID: 251943, firstName: "Mike", lastName: "Miller")
        var results: [Student: Double] = [:]
        results[larry] = 10.75
        results[mike] = 10.69
        XCTAssert(results[mike] < results[larry])
    }