How to Compare Custom Objects in Swift

Swift is a strong and flexible programming language with a large number of protocols, which is one of its advantages. The Comparable protocol is notable among them all because it lets you specify the comparison capabilities of instances of a certain type. This blog post will explain the Comparable protocol, show you how to construct custom objects comply with it, and provide real-world examples to help you better understand how to use it. Also, Swift class extensions have been added as a learning experience.

What is the Comparable Protocol?

The Comparable protocol is used to define how instances of a type are compared to each other. When a type conforms to Comparable, you can use comparison operators like <, <=, >, and >= on instances of that type. This is particularly useful when you want to sort an array of custom objects or perform other comparison-based operations.

Here’s a simple example of a type conforming to Comparable:

struct Person: Comparable {
    let name: String
    let age: Int
    
    static func < (lhs: Person, rhs: Person) -> Bool {
        return lhs.age < rhs.age
    }
    
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.age == rhs.age
    }
}

In this example, the Person struct conforms to Comparable by implementing the < and == operators. The comparison is based on the age property of Person.

Implementing Comparable in Custom Types

To make a custom type conform to Comparable, you need to implement two operators: the less-than operator (<) and the equality operator (==). The less-than operator defines the ordering of instances, while the equality operator checks if two instances are equal.

Let’s break down the process step by step.

Step 1: Define the Custom Type

First, define your custom type. For this example, let’s create a Book struct:

struct Book {
    let title: String
    let author: String
    let publicationYear: Int
}
Step 2: Conform to the Comparable Protocol

Next, make the Book struct conform to Comparable by implementing the required operators:

extension Book: Comparable {
    static func < (lhs: Book, rhs: Book) -> Bool {
        return lhs.publicationYear < rhs.publicationYear
    }
    
    static func == (lhs: Book, rhs: Book) -> Bool {
        return lhs.title == rhs.title && lhs.author == rhs.author && lhs.publicationYear == rhs.publicationYear
    }
}

In this example, the Book struct is compared based on the publicationYear property. The == operator checks for equality by comparing all properties of the Book struct.

Step 3: Using Comparable

Now that your Book struct conforms to Comparable, you can use comparison operators and functions like sort() and sorted():

let book1 = Book(title: "1984", author: "George Orwell", publicationYear: 1949)
let book2 = Book(title: "Brave New World", author: "Aldous Huxley", publicationYear: 1932)
let book3 = Book(title: "Fahrenheit 451", author: "Ray Bradbury", publicationYear: 1953)

let books = [book1, book2, book3]

let sortedBooks = books.sorted()
for book in sortedBooks {
    print("\(book.title) by \(book.author), published in \(book.publicationYear)")
}

Output:

Brave New World by Aldous Huxley, published in 1932
1984 by George Orwell, published in 1949
Fahrenheit 451 by Ray Bradbury, published in 1953

The books are sorted by their publication year in ascending order.

Practical Applications

Conforming to the Comparable protocol is useful in many real-world scenarios. Here are a few practical applications:

  1. Sorting Lists: When you have a list of custom objects, such as products, students, or events, you can easily sort them based on certain criteria.
  2. Binary Search: If your custom objects are comparable, you can perform binary search operations on sorted collections for efficient lookups.
  3. Finding Extremes: You can quickly find the minimum or maximum objects in a collection based on their properties.

Custom Comparison Logic

Sometimes, you might want to compare custom objects based on multiple properties. You can achieve this by modifying the comparison logic in the < operator. Let’s modify the Book struct to compare first by publicationYear and then by title if the years are the same:

extension Book: Comparable {
    static func < (lhs: Book, rhs: Book) -> Bool {
        if lhs.publicationYear == rhs.publicationYear {
            return lhs.title < rhs.title
        }
        return lhs.publicationYear < rhs.publicationYear
    }
    
    static func == (lhs: Book, rhs: Book) -> Bool {
        return lhs.title == rhs.title && lhs.author == rhs.author && lhs.publicationYear == rhs.publicationYear
    }
}

Now, the Book struct will first compare by publicationYear and use title as a tiebreaker:

let book4 = Book(title: "Animal Farm", author: "George Orwell", publicationYear: 1945)
let book5 = Book(title: "1984", author: "George Orwell", publicationYear: 1945)

let books = [book4, book5, book1, book2, book3]

let sortedBooks = books.sorted()
for book in sortedBooks {
    print("\(book.title) by \(book.author), published in \(book.publicationYear)")
}

Output:

Animal Farm by George Orwell, published in 1945
1984 by George Orwell, published in 1945
Brave New World by Aldous Huxley, published in 1932
1984 by George Orwell, published in 1949
Fahrenheit 451 by Ray Bradbury, published in 1953

Conclusion

The Comparable protocol in Swift is a powerful tool that allows you to define how custom objects should be compared. By conforming to this protocol, you can leverage Swift’s built-in comparison operators and sorting functions, making your code more expressive and easier to manage.

To summarize, here’s how you can make your custom types comparable:

  1. Define your custom type (e.g., Book).
  2. Conform to the Comparable protocol by implementing the < and == operators.
  3. Use comparison operators and functions like sort() and sorted() to work with your comparable objects.

By following these steps, you can create custom objects that integrate seamlessly with Swift’s powerful comparison and sorting capabilities. Whether you’re working on a small project or a large application, understanding and utilizing the Comparable protocol will help you write cleaner, more efficient code.