Categories
Foundation iOS macOS Swift

Sorting data with KeyPathComparator

KeyPathComparator was added to Foundation in iOS 15 and macOS 12. The KeyPathComparator is used by defining a key path which is used for fetching a value for comparison. Values are then compared with a SortComparator. In the simplest form we do not need to create the SortComparator ourselves and instead, ComparableComparator is created automatically. But if the value is String then String.StandardComparator.localizedStandard is used instead of ComparableComparator. All in all it is pretty much the similar to NSSortDescriptor which was used for sorting NSArray and NSMutableArray. New comparator types on the other hand can be used with many more types.

Using KeyPathComparator

As an example, let’s take a case of having an array of Player types where each player has played two rounds and therefore have two different scores. Additionally, each player type stores a competitor number as well.

struct Player {
let competitorNumber: Int
let round1: Int
let round2: Int
}
let players = [
Player(competitorNumber: 1, round1: 75, round2: 69),
Player(competitorNumber: 2, round1: 31, round2: 93),
Player(competitorNumber: 3, round1: 91, round2: 88),
Player(competitorNumber: 4, round1: 84, round2: 62),
Player(competitorNumber: 5, round1: 88, round2: 20),
]
view raw Player.swift hosted with ❤ by GitHub

If we want to sort the array of players by first and second round scores then it goes like this (note that order is set to reverse which gives us descending order):

let round1 = players.sorted(using: KeyPathComparator(\.round1, order: .reverse))
/*
player3 round1=91 round2=88
player5 round1=88 round2=20
player4 round1=84 round2=62
player1 round1=75 round2=69
player2 round1=31 round2=93
*/
let round2 = players.sorted(using: KeyPathComparator(\.round2, order: .reverse))
/*
player2 round1=31 round2=93
player3 round1=91 round2=88
player1 round1=75 round2=69
player4 round1=84 round2=62
player5 round1=88 round2=20
*/
view raw Sorted.swift hosted with ❤ by GitHub

Here we can see that sequences have sorted(using:) functions which take in either one comparator or several. An example of using several comparators is sorting the same players array by the highest score first and if two or more players hace the same highest score, then sorting by the worst score from these two rounds.

extension Player {
var bestRound: Int {
max(round1, round2)
}
var worstRound: Int {
min(round1, round2)
}
}
let maxScore = players.sorted(using: [
KeyPathComparator(\.bestRound, order: .reverse),
KeyPathComparator(\.worstRound, order: .reverse)
])
/*
Sorted by max score
player2 round1=31 round2=93
player3 round1=91 round2=88 <– equal best score
player5 round1=88 round2=20 <– equal best score
player4 round1=84 round2=62
player1 round1=75 round2=69
*/

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.