Overview
Array sorting is the process of arranging elements in a specific order – either ascending or descending. Swift provides several built-in functions to sort arrays, allowing developers to write clean and efficient code. In this practical article, we’ll discuss these functions, along with custom sorting techniques and best practices for sorting arrays in Swift. We’ll also walk through various real-world examples of sorting arrays of numbers, strings, objects, and dictionaries in Swift in order from basic to advanced, from simple to complex. By the end of the day, you’ll have a solid understanding of Swift’s array sorting capabilities and best practices for implementing them in your projects.
Understanding Swift’s Built-in Sorting Functions
Swift’s built-in array sorting functions are designed for simplicity and ease of use. There are 2 primary sorting functions that you’ll need to know:
- sorted(): This function returns a new array containing the sorted elements of the original array. It does not modify the original array.
- sort(): This function sorts the elements of the original array in place, modifying it directly.
Both of these functions can be used with or without a custom sorting closure, allowing you to define your own sorting logic if necessary. In the following sections, we’ll explore these functions in detail and see how they can be applied to various types of arrays.
Sorting Arrays of Numbers in Swift
Ascending order & descending order
Sorting arrays of numbers is simple in Swift, thanks to the sorted() function. By default, sorted() will rearrange the elements in ascending order.
Example:
let numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
let sortedNumbers = numbers.sorted()
print(sortedNumbers)
// Output: [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
If you need to sort the numbers in descending order, just pass the > (greater than) operator as a parameter:
let numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
let descendingSortedNumbers = numbers.sorted(by: >)
print(descendingSortedNumbers)
// Output: [9, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1]
Adding custom sorting closure
If you need more control over the sorting process, you can provide a custom sorting closure to the sort() or sorted() functions. Let’s say you want to sort an array of integers based on their absolute values. You can achieve this by using a custom sorting closure as shown below:
let numbersWithNegatives = [3, -1, 4, -1, 5, -9, 2, 6, 5, 3, 5]
let sortedByAbsoluteValue = numbersWithNegatives.sorted { abs($0) < abs($1) }
print(sortedByAbsoluteValue)
// Output: [-1, -1, 2, 3, 3, 4, 5, 5, 5, 6, -9]
Notice how the closure takes 2 arguments, $0 and $1, which represent two elements in the array. The closure returns true if the first element should come before the second element in the sorted order and false otherwise.
Sorting Arrays of Strings in Swift
Alphabetical Sorting
Sorting arrays of strings is just as straightforward as sorting arrays of numbers. By default, the sorted() and sort() functions will sort strings in alphabetical order:
let words = ["apple", "banana", "cherry", "date", "fig", "grape", "kiwi", "lemon", "mango", "nectarine", "orange", "papaya", "quince", "raspberry", "strawberry", "tangerine", "watermelon"]
let sortedWords = words.sorted()
print(sortedWords)
Output:
["apple", "banana", "cherry", "date", "fig", "grape", "kiwi", "lemon", "mango", "nectarine", "orange", "papaya", "quince", "raspberry", "strawberry", "tangerine", "watermelon"]
Case-Insensitive Sorting
By default, Swift’s sorting functions are case-sensitive, meaning that uppercase letters will come before lowercase letters (if you choose ascending order). To perform case-insensitive sorting, you can use the localizedCompare() function in a custom sorting closure:
import Foundation
let mixedCaseWords = ["Apple", "banana", "Cherry", "Date", "fig", "grape", "kiwi", "Lemon", "Mango", "nectarine", "orange", "Papaya", "quince", "raspberry", "STRAWBERRY", "TANGERINE", "watermelon"]
let caseInsensitiveSortedWords = mixedCaseWords.sorted { $0.localizedCompare($1) == .orderedAscending }
print(caseInsensitiveSortedWords)
Output:
["Apple", "banana", "Cherry", "Date", "fig", "grape", "kiwi", "Lemon", "Mango", "nectarine", "orange", "Papaya", "quince", "raspberry", "STRAWBERRY", "TANGERINE", "watermelon"]
Note: Don’t forget to import Foundation.
Sorting Arrays of Objects in Swift
In a case where you have an array of custom objects, you are very likely to want to sort the array based on one or some of the object’s properties. Suppose you have the following Person class:
class Person {
let firstName: String
let lastName: String
let age: Int
init(firstName: String, lastName: String, age: Int) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
}
To sort an array that contains Person objects by their age property, you can use a custom sorting closure as follows:
// SlingAcademy.com
// main.swift
class Person {
let firstName: String
let lastName: String
let age: Int
init(firstName: String, lastName: String, age: Int) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
}
// An array of Person objects
let people: [Person] = [
Person(firstName: "Elon", lastName: "Musk", age: 55),
Person(firstName: "Bill", lastName: "Gates", age: 69),
Person(firstName: "John", lastName: "Doe", age: 30),
Person(firstName: "Red", lastName: "Robot", age: 99),
Person(firstName: "Sling", lastName: "Academy", age: 5),
]
let peopleSortedByAge = people.sorted { $0.age < $1.age }
dump(peopleSortedByAge)
Output:
▿ 5 elements
▿ main.Person #0
- firstName: "Sling"
- lastName: "Academy"
- age: 5
▿ main.Person #1
- firstName: "John"
- lastName: "Doe"
- age: 30
▿ main.Person #2
- firstName: "Elon"
- lastName: "Musk"
- age: 55
▿ main.Person #3
- firstName: "Bill"
- lastName: "Gates"
- age: 69
▿ main.Person #4
- firstName: "Red"
- lastName: "Robot"
- age: 99
Custom Comparisons with Comparable Protocol
For more complex sorting cases such as sorting by multiple criteria, you can make your custom object conform to the Comparable protocol. This requires implementing the < (less than) operator for your object, which will be used by Swift’s sorting functions.
Continuing with the Person class example, let’s make it conform to the Comparable protocol to automatically sorts by lastName (primary criterion), then by firstName (secondary criterion):
extension Person: Comparable {
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName && lhs.age == rhs.age
}
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.lastName < rhs.lastName || (lhs.lastName == rhs.lastName && lhs.firstName < rhs.firstName)
}
}
Complete example:
// SlingAcademy.com
// main.swift
class Person {
let firstName: String
let lastName: String
let age: Int
init(firstName: String, lastName: String, age: Int) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
}
// sort by lastName (primary criterion) and firstName (secondary criterion)
extension Person: Comparable {
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName && lhs.age == rhs.age
}
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.lastName < rhs.lastName || (lhs.lastName == rhs.lastName && lhs.firstName < rhs.firstName)
}
}
// An array of Person objects
let people: [Person] = [
Person(firstName: "Elon", lastName: "Musk", age: 55),
Person(firstName: "Bill", lastName: "Gates", age: 69),
Person(firstName: "John", lastName: "Doe", age: 30),
Person(firstName: "Red", lastName: "Robot", age: 99),
Person(firstName: "Sling", lastName: "Academy", age: 5),
]
// Automatically sorts by lastName, then by firstName
let sortedPeople = people.sorted()
dump(sortedPeople)
Output:
▿ 5 elements
▿ main.Person #0
- firstName: "Sling"
- lastName: "Academy"
- age: 5
▿ main.Person #1
- firstName: "John"
- lastName: "Doe"
- age: 30
▿ main.Person #2
- firstName: "Bill"
- lastName: "Gates"
- age: 69
▿ main.Person #3
- firstName: "Red"
- lastName: "Robot"
- age: 99
▿ main.Person #4
- firstName: "Elon"
- lastName: "Musk"
- age: 55
Sorting Arrays of Dictionaries in Swift
Sorting by Key Values
If you have an array of dictionaries, you might want to sort the array based on the values of a specific key. Here’s an example with an array of dictionaries representing students and their scores:
let students: [[String: Any]] = [
["name": "Alice", "score": 95],
["name": "Bob", "score": 88],
["name": "Charlie", "score": 92],
["name": "David", "score": 90],
]
let sortedByScore = students.sorted { ($0["score"] as! Int) < ($1["score"] as! Int) }
print(sortedByScore)
Output:
[
["name": "Bob", "score": 88],
["name": "David", "score": 90],
["name": "Charlie", "score": 92],
["name": "Alice", "score": 95]
]
Sorting by Multiple Criteria
To sort an array of dictionaries by multiple criteria, you can use a custom sorting closure that compares the values of multiple keys:
let students: [[String: Any]] = [
["name": "Alice", "score": 95],
["name": "Bob", "score": 88],
["name": "Charlie", "score": 92],
["name": "Sling Academy", "score": 95],
["name": "David", "score": 90],
]
// sort by score and name (if score is the same)
let sortedByNameAndScore = students.sorted {
if ($0["score"] as! Int) != ($1["score"] as! Int) {
return ($0["score"] as! Int) < ($1["score"] as! Int)
} else {
return ($0["name"] as! String) < ($1["name"] as! String)
}
}
print(sortedByNameAndScore)
Output:
[
["name": "Bob", "score": 88],
["name": "David", "score": 90],
["name": "Charlie", "score": 92],
["name": "Alice", "score": 95],
["name": "Sling Academy", "score": 95]
]
Advanced Sorting Techniques in Swift
Using Higher-Order Functions
Swift’s higher-order functions, such as map(), filter(), and reduce(), can be used in conjunction with sorting functions to perform complex sorting tasks.
For example, you can use the map() function to create a new array containing only the names of the students, and then sort that array alphabetically:
let students: [[String: Any]] = [
["name": "Alice", "score": 95],
["name": "Bob", "score": 88],
["name": "Charlie", "score": 92],
["name": "Sling Academy", "score": 95],
["name": "David", "score": 90],
]
// Sort array of student names in alphabetical order
let studentNames = students.map { $0["name"] as! String }.sorted()
print(studentNames)
Output:
["Alice", "Bob", "Charlie", "David", "Sling Academy"]
Implementing Custom Sort Descriptors
For more advanced sorting tasks, you can create custom sort descriptor objects that encapsulate your sorting logic. This can be useful for sorting arrays based on multiple criteria or implementing custom sorting algorithms.
Here’s an example of a custom sort descriptor that sorts an array of Person objects by their lastName property, and then by their firstName property (this example produces the same result as one of the preceding examples, but it uses a different approach):
// SlingAcademy.com
// main.swift
class Person {
let firstName: String
let lastName: String
let age: Int
init(firstName: String, lastName: String, age: Int) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
}
struct PersonSortDescriptor {
let keyPath: KeyPath<Person, String>
let ascending: Bool
func compare(_ person1: Person, _ person2: Person) -> Bool {
let value1 = person1[keyPath: keyPath]
let value2 = person2[keyPath: keyPath]
return ascending ? value1 < value2 : value1 > value2
}
}
let lastNameDescriptor = PersonSortDescriptor(keyPath: \Person.lastName, ascending: true)
let firstNameDescriptor = PersonSortDescriptor(keyPath: \Person.firstName, ascending: true)
// An array of Person objects
let people: [Person] = [
Person(firstName: "Elon", lastName: "Musk", age: 55),
Person(firstName: "Bill", lastName: "Gates", age: 69),
Person(firstName: "John", lastName: "Doe", age: 30),
Person(firstName: "Red", lastName: "Robot", age: 99),
Person(firstName: "Sling", lastName: "Academy", age: 5),
]
let sortedPeople = people.sorted { person1, person2 in
if lastNameDescriptor.compare(person1, person2) {
return true
} else if lastNameDescriptor.compare(person2, person1) {
return false
} else {
return firstNameDescriptor.compare(person1, person2)
}
}
dump(sortedPeople)
Output:
▿ 5 elements
▿ main.Person #0
- firstName: "Sling"
- lastName: "Academy"
- age: 5
▿ main.Person #1
- firstName: "John"
- lastName: "Doe"
- age: 30
▿ main.Person #2
- firstName: "Bill"
- lastName: "Gates"
- age: 69
▿ main.Person #3
- firstName: "Elon"
- lastName: "Musk"
- age: 55
▿ main.Person #4
- firstName: "Red"
- lastName: "Robot"
- age: 99
Best Practices for Array Sorting in Swift
Some things that can be very helpful for you when dealing with array sorting stuff in Swift:
- Use Swift’s built-in sorting functions (sorted() and sort()) whenever possible, as they are optimized for performance and readability.
- When sorting arrays of custom objects or dictionaries, provide a custom sorting closure that clearly expresses your sorting logic.
- Make your custom objects conform to the Comparable protocol for more complex sorting tasks or when you want to define a default sorting order.
- Use higher-order functions (map(), filter(), and reduce()) in conjunction with sorting functions to perform more complex sorting tasks.
Final Words
Array sorting is an essential skill for any Swift developer, and by mastering the techniques discussed in this tutorial, you’ll be well-equipped to handle a variety of sorting tasks in your projects. Swift’s built-in sorting functions, along with custom sorting closures and sort descriptors, provide a powerful set of tools for sorting arrays of numbers, strings, objects, and dictionaries. Remember to follow best practices for array sorting, and don’t be afraid to experiment with different techniques to find the best solution for your specific task.
Now that you have a thorough understanding of Swift’s array sorting capabilities, it’s time to put your knowledge into practice. Try implementing some of the examples discussed in this guide, or come up with your own sorting tasks to tackle. With practice and experience, you’ll become a master of Swift’s array sorting functions in no time.
Happy coding & have a nice day!