Skip to Content
Course content

65: Sorting and Custom Comparators

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Swift's built-in sorting is a breeze when you're dealing with Ints or Strings, but as soon as you start working with your own custom models, the compiler is going to stop you in your tracks. It doesn't know if a "User" should be sorted by their join date, their username, or their account balance. That's where custom comparators come in.

I've found that the most practical way to master this is to build a priority queue of sorts. Let's imagine we're building a simple task manager. We don't just want to sort by the name of the task; we want to sort by priority level, and if two tasks have the same priority, we want the one due sooner to come first.

Defining the Task model

First, we need a data structure. I'll keep this lean. We need a title, a priority level (where a higher number means more urgent), and a deadline.

struct Task {
    let title: String
    let priority: Int
    let dueDate: Date
}

let myTasks = [
    Task(title: "Fix critical login bug", priority: 3, dueDate: Date().addingTimeInterval(86400)),
    Task(title: "Update documentation", priority: 1, dueDate: Date().addingTimeInterval(172800)),
    Task(title: "Email client", priority: 2, dueDate: Date().addingTimeInterval(43200)),
    Task(title: "Refactor API layer", priority: 3, dueDate: Date().addingTimeInterval(20000))
]

The basic sorted closure

If I just call myTasks.sorted(), Swift will throw a compiler error because Task doesn't conform to Comparable. I could make it conform, but often you want to sort the same list in different ways depending on the screen the user is looking at. The sorted(by:) method is much more flexible here because it takes a closure that returns a Boolean.

Let's start by sorting just by priority. I want the highest priority at the top.

let prioritySorted = myTasks.sorted { $0.priority > $1.priority }

The $0 and $1 are just shorthand for the two elements Swift is currently comparing. If the closure returns true, the first element comes before the second.

Correcting a logic flip

Here is where I usually trip up when I'm rushing. I'll write a sort for the dates, thinking "earlier is better," and I'll end up with a list that's completely backwards.

// My first attempt at sorting by date
let dateSorted = myTasks.sorted { $0.dueDate > $1.dueDate }

Wait. I just realized my list is showing the tasks due furthest in the future first. Because I used the "greater than" operator, I'm telling Swift that a date further in the future is "less than" (should come before) an earlier date in terms of sorting order. That's not what I want for a deadline list.

I need to flip that operator to < so that the earliest date (the smallest value) takes precedence:

let dateSortedCorrected = myTasks.sorted { $0.dueDate < $1.dueDate }

Building a multi-level comparator

Now for the real-world scenario. A single-property sort is rarely enough. We want our tasks sorted by priority first, but we need a "tie-breaker" for when priorities are equal. In this case, the tie-breaker is the due date.

To do this, we move away from the shorthand $0 syntax and write a more explicit closure. This makes the logic much easier to read and maintain.

let finalSortedTasks = myTasks.sorted { (task1, task2) -> Bool in
    if task1.priority != task2.priority {
        // Different priorities? Sort by the highest priority first.
        return task1.priority > task2.priority
    } else {
        // Same priority? Use the date as the tie-breaker (earliest first).
        return task1.dueDate < task2.dueDate
    }
}

Now, the "Refactor API layer" task (Priority 3, sooner date) will correctly sit above the "Fix critical login bug" task (Priority 3, later date), and both will sit above the lower priority tasks. This pattern—checking for inequality and then falling back to a second property—is the bread and butter of professional data sorting in Swift.




📋 Practical Task

Building a Game Leaderboard with Tie-Breaking Logic

You are building a leaderboard for a mobile game. You have a Player struct with two properties: score: Int and timeTaken: Double (representing the seconds it took to complete the level).

Your Goal: Create a sorted list of players based on these rules:

  1. Players with a higher score must appear first.
  2. If two players have the exact same score, the player who completed the level in less time must appear first.

Implement the Player struct and a sorting mechanism for the following data set:

let players = [
    Player(name: "Alice", score: 100, timeTaken: 45.2),
    Player(name: "Bob", score: 150, timeTaken: 60.1),
    Player(name: "Charlie", score: 150, timeTaken: 55.5),
    Player(name: "Diana", score: 80, timeTaken: 30.0)
]

Print the final sorted list to the console to verify that Bob and Charlie are at the top, with Charlie appearing before Bob due to his faster time.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.