Start learning free

LESSON 223 OF 415 · SORTING

Sorting in Swift: sort(), sorted() and Custom Closures

Lesson Read the full explanation

In order to sort an array we can use the .sort() method which sorts the elements of a given array in ascending order (default), descending order, or with a custom closure.

Similarly, .sorted() returns a new sorted array without modifying the original.

Important: .sort() changes the array it is called on, so the array must be declared with var. A let array can only be sorted with .sorted(), which returns a new array.

Syntax:

Let's see some examples below. Sorting an array of integers in ascending order

var numbers = [11, 3, 7, 5, 2]

numbers.sort() // numbers becomes [2, 3, 5, 7, 11]

Sorting an array of integers in ascending order using .sorted()

var arr = [11, 3, 7, 5, 2]

let sorted = arr.sorted() // sorted becomes [2, 3, 5, 7, 11]

Sorting an array of integers in descending order

var numbers = [11, 3, 7, 5, 2]

numbers.sort(by: >) // numbers becomes [11, 7, 5, 3, 2]

In Swift, sorting in reverse is simple: just pass by: >. The operator > is itself a function that takes two values and returns a Bool, so it works for any element type that can be compared with it.


Sorting an array of Strings in descending order

var names = ["Mircea", "Andy", "Xavier", "Michael", "Christina"]

names.sort(by: >) // --> ["Xavier", "Mircea", "Michael", "Christina", "Andy"]

Sort with custom closure (comparator):

The .sort() method supports a closure that we can use to specify how we want to sort. The closure takes two elements and returns Bool: return true if the first element should come before the second.

func compare(_ a: Int, _ b: Int) -> Bool {
    return a < b
}

The same function can be written as a closure expression (a nameless function we can easily pass to sort()) as follows:

{ (a: Int, b: Int) -> Bool in
    return a < b
}

If the closure just returns one thing, we can write it as follows:

{ a, b in a < b }

When the sort() method compares two values, it sends the values to the closure, and sorts the values according to the returned Bool:

  • If the result is true, a is sorted before b.
  • If the result is false, b is sorted before a.

All we need to do is pass the closure to the sort() method, either in parentheses or, as below, as a trailing closure written after the method name:

var numbers = [3, 23, 12]

// using the short version of the closure
numbers.sort { a, b in a < b } // --> [3, 12, 23]

To demonstrate a basic example, let's assume the requirement for ordering a specific list is the length of the strings in the list, shortest to longest. We will be using the count property of each string to compare them.

var words = ["banana", "pie", "Washington", "book"]

words.sort { a, b in a.count < b.count } // --> ["pie", "book", "banana", "Washington"]

Example: sorting array of pairs

Let's say we want to sort an array, where each element is another array consisting of two numbers.

We will be using [[Int]] — an array of Int arrays — which can be sorted with a custom closure.

var pairs = [[1, 5], [12, 5], [10, 1], [1, 3]]

pairs.sort { a, b in
    // in case the first values of each pair are equal,
    // compare them using their second values
    if a[0] == b[0] {
        return a[1] < b[1]
    }
    return a[0] < b[0]
}
// pairs --> [[1, 3], [1, 5], [10, 1], [12, 5]]

Time Complexity:

Sorting an array via .sort() takes O(n * log n * C) time, where C is the time taken to compare two elements from the array.

It takes O(1) (constant time) to compare two numbers, therefore sorting an array of numbers takes O(n log n).

However, it takes O(L) to compare two strings, therefore sorting an array of strings takes O(n * log n * L), where L is the average length of the strings.


Space Complexity:

The sort() method sorts the elements of an array in place (transforms the array directly, using no auxiliary data structure), so no second array is created for the result. Internally the sort may use some temporary memory, so the extra space is O(1) to O(n) depending on the implementation; .sorted() needs O(n) for the new array.


Assignment
Follow the Coding Tutorial and let's sort some arrays.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 9
    Ctrl + Enter to check

    SWIFT · SORTING

    Sorting in Swift, explained

    sort() and sorted()

    Swift arrays have two ways to sort, and the difference matters:

    • sort() sorts the array in place. It changes the array it is called on and returns nothing, so the array must be declared with var.
    • sorted() leaves the array alone and returns a new sorted array. It works on a let constant, and on function parameters, which are constants in Swift.
    var numbers = [11, 3, 7, 5, 2]
    numbers.sort()
    print(numbers)          // [2, 3, 5, 7, 11]
    
    let original = [11, 3, 7, 5, 2]
    let ascending = original.sorted()
    print(original)         // [11, 3, 7, 5, 2]
    print(ascending)        // [2, 3, 5, 7, 11]
    

    Without a comparison, both sort ascending. This works for any element type that can be compared with <, such as Int, Double and String.

    Descending order with by:

    Both methods have a form that takes a function, sort(by:) and sorted(by:). The function receives two elements and returns true when the first one must come before the second. The operator > is itself such a function, so it can be passed as it is:

    var numbers = [11, 3, 7, 5, 2]
    numbers.sort(by: >)
    print(numbers)          // [11, 7, 5, 3, 2]
    

    This works the same way for every element type, including strings. No separate reverse step is needed.

    Custom orderings with closures

    For any other order, pass a closure. These three forms do the same thing, from the longest to the shortest:

    func compare(_ a: Int, _ b: Int) -> Bool {
        return a < b
    }
    
    // a closure with typed parameters
    let typed = { (a: Int, b: Int) -> Bool in
        return a < b
    }
    
    // a closure with inferred types, passed straight to sorted(by:)
    let result = [3, 23, 12].sorted(by: { a, b in a < b })
    print(result)           // [3, 12, 23]
    

    A closure written after the call's parentheses is a trailing closure. When it has a single expression, the return can be dropped, and the parameters can be left unnamed and called $0 and $1 instead:

    var words = ["banana", "pie", "Washington", "book"]
    words.sort { $0.count < $1.count }
    print(words)            // ["pie", "book", "banana", "Washington"]
    

    Here $0 and $1 are the two strings being compared. If the closure returns true, the first goes before the second; if it returns false, it doesn't.

    Sorting pairs

    A closure with several statements needs an explicit return. This is how you sort an array of two-element arrays, using the second value when the first values are equal:

    var pairs = [[1, 5], [12, 5], [10, 1], [1, 3]]
    pairs.sort {
        if $0[0] == $1[0] {
            return $0[1] < $1[1]
        }
        return $0[0] < $1[0]
    }
    print(pairs)            // [[1, 3], [1, 5], [10, 1], [12, 5]]
    

    Gotchas

    • sort() on a constant doesn't compile. Calling sort() on a let array, or on a function parameter, gives cannot use mutating member on immutable value: 'numbers' is a 'let' constant. Use sorted(), or copy the parameter first with var nums = nums.
    • sort() returns nothing. Writing let result = numbers.sort() doesn't give you the array. Swift warns that the constant has type (), an empty value. Use sorted() when you want a result.
    • Strings compare by character value. Every uppercase letter comes before every lowercase letter, so ["Banana", "apple", "Cherry"].sorted() gives ["Banana", "Cherry", "apple"]. To ignore case, compare lowercased versions in a closure.
    • The closure decides "comes first". It should say whether the first argument goes before the second. Using <=, which is true for equal elements, breaks that rule and can give unreliable results. Use a strict comparison.

    The lesson's exercise

    The exercise has three parts, one for each kind of sort.

    1. Numbers, descending. Declare numbers as [2, 1, 3, 10] with var and call numbers.sort(by: >). It becomes [10, 3, 2, 1].
    2. Pairs, first value ascending, second value descending. Declare coordinates as [3, 2], [2, 3] and [3, 5]. In the closure, when $0[0] == $1[0] return $0[1] > $1[1]; otherwise return $0[0] < $1[0]. The result is [[2, 3], [3, 5], [3, 2]]: the pair starting with 2 comes first, and of the two pairs starting with 3, the one with the bigger second value comes first.
    3. Names, ignoring case. Declare names as ["Mircea", "john", "Andy"] and sort with { $0.lowercased() < $1.lowercased() }. The result is ["Andy", "john", "Mircea"]. A plain sort() would put "john" last.
    var numbers = [2, 1, 3, 10]
    numbers.sort(by: >)
    
    var coordinates = [
        [3, 2],
        [2, 3],
        [3, 5]
    ]
    coordinates.sort {
        if $0[0] == $1[0] {
            return $0[1] > $1[1]
        }
        return $0[0] < $1[0]
    }
    
    var names = ["Mircea", "john", "Andy"]
    names.sort { $0.lowercased() < $1.lowercased() }
    

    Cost

    Sorting n elements takes O(n log n) comparisons, and each comparison costs time too. Comparing two numbers is constant time, so sorting numbers is O(n log n). Comparing two strings takes time proportional to their length, so sorting strings of average length L costs O(n log n * L). sorted() also builds a second array of n elements, so it uses O(n) extra memory.

    Key takeaways

    • sort() changes a var array in place; sorted() returns a new array and works on constants.
    • by: > sorts in descending order for numbers and strings alike.
    • A sorting closure takes two elements and returns true when the first goes first. $0 and $1 are the shorthand names for them.
    • To sort by several keys, compare the first key and fall back to the next one only when the first keys are equal.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free