While working on some code at work, I ran into a simple sorting problem in some of my Swift code. Suppose we have a struct like this:

struct User {
    let uid: UUID
    let firstName: String
    let lastName: String
    let age: UInt8

    init(firstName: String, lastName: String, age: UInt8) {
        self.uid = UUID()
        self.firstName = firstName
        self.lastName = lastName
        self.age = age

Let’s say that we have a [User]. How could we sort that array first by lastName ascending and, in the case of ties, second by the age ascending?

Well, if we can map our structure into a tuple of the properties we care about, then we can use the standard library’s tuple comparison functions! The standard library has several overloads of < for various sized tuples that are composed of Comparable types. These implementations will compare the first values of the tuples together, and, in the case of a tie, continue to compare the next tuple values.

In order to sort by last name and break sorting ties with age, we map our users into tuples of (lastName, age) and then compare them with <:

let users = [
    User(firstName: "James", lastName: "Smith", age: 27),
    User(firstName: "Michael", lastName: "Smith", age: 30),
    User(firstName: "Maria", lastName: "Garcia", age: 28),
    User(firstName: "James", lastName: "Johnson", age: 26)

let sortedUsersByLastNameAscendingAgeAscending = users.sorted { u1, u2 in
    return (u1.lastName.lowercased(), u1.age) < (u2.lastName.lowercased(), u2.age)

You may, however, want to sort different dimensions of your tuple in different sort orders. Suppose we wanted to sort first by lastName ascending, but, in the case of ties, second by age descending. This is the type of problem that came up at work. Luckily, I was able to think of a small trick to make this possible with minimal code. Simply switch the value you would normally place in the first tuple with the value you would normally place in the second tuple.

let sortedUsersByLastNameAscendingAgeDescending = users.sorted { u1, u2 in
      // We are sorting by `lastName` ascending, but we are
      // breaking ties based on `age` in descending order.
      // Notice that u2.age is in the left tuple!
    return (u1.lastName.lowercased(), u2.age) < (u2.lastName.lowercased(), u1.age)

Tricks like this can lead to very concise code, but are often either hard to read or can go unnoticed by the reader. In the case of using the above sorting technique, I think it is worth adding a comment to call attention to what you are doing.