Start learning free

LESSON 268 OF 678 · TUPLES, SORTING & BUILT-INS

Custom Sorting in Swift: sorted with a Closure and Two Rules

Lesson Read the full explanation

TL ; DR:

Give sorted a closure. It gets two items and returns true when the first one goes first. For two rules, give the closure a body with an if.


let nums = [25, 13, 5, 41, 33]

let byLastDigit = nums.sorted { a, b in
    if a % 10 != b % 10 {
        return a % 10 < b % 10
    }
    return a < b
}
// [41, 13, 33, 5, 25]

let scores = [70, 95, 82]

let highFirst = scores.sorted { $0 > $1 }

// [95, 82, 70]


Full lesson:

In Closures & Sorting you passed a closure to sorted. A closure is a small function without a name. It gets two items. It returns true when the first must go before the second.

  • High to low: sorted { $0 > $1 }.
  • Shortest first: sorted { $0.count < $1.count }.

Sometimes one rule is not enough. Two numbers with the same last digit need a second rule, like small to large.

A closure with two rules needs more than one line. Name its two items with a, b in at the start. Names read better than $0 and $1 here.

Check the first rule. Use the second one only on a tie, as the if above does. Above, the first rule compares the last digits, a % 10 and b % 10.

Comparing two strings with < puts them from A to Z.

Remember the difference. The sorted method makes a new array. The sort method sorts a var array itself.


Your task

A pet shop sells toys. Their prices are in prices. Make an array named highFirst with the prices from high to low.

The shop's pets are in names. Make an array named byLastLetter with the names sorted by their last letter, in alphabetical order. Names with the same last letter go from A to Z.

The code already prints both, one item per line.

Expected output:

30
12
8
5
Bella
Luna
Bo
Coco
Max
Rex
STEP 1 OF 3
    Ctrl + Enter to check

    SWIFT · TUPLES, SORTING & BUILT-INS

    Custom Sorting in Swift, explained

    A closure that decides the order

    sorted can take a closure: a small function without a name. The closure gets two items and returns true when the first must go before the second. For simple orders, one line is enough:

    let scores = [70, 95, 82]
    let highFirst = scores.sorted { $0 > $1 }
    print(highFirst)
    
    [95, 82, 70]
    

    Here $0 and $1 are the two items, and a larger first item goes first. Shortest first is sorted { $0.count < $1.count }.

    Two rules in one closure

    Sometimes one rule is not enough. Two numbers with the same last digit need a second rule, such as small to large. A closure with two rules needs more than one line. Name its two items with a, b in at the start, since names read better than $0 and $1 here. Check the first rule, and use the second one only on a tie:

    let nums = [25, 13, 5, 41, 33]
    let byLastDigit = nums.sorted { a, b in
        if a % 10 != b % 10 {
            return a % 10 < b % 10
        }
        return a < b
    }
    print(byLastDigit)
    
    [41, 13, 33, 5, 25]
    

    41 comes first because its last digit is 1. 13 and 33 share the last digit 3, so the second rule puts the smaller number, 13, first. 5 and 25 both end in 5, and again 5 comes before 25.

    The pattern is always the same: if the first rule's values differ, return its answer; otherwise fall through to the next rule.

    Things to know

    • Strings compare with <. "Max" < "Rex" is true: the first string comes first from A to Z. Uppercase letters sort before lowercase ones, which doesn't matter for the capitalised names in the exercise.
    • sorted versus sort. sorted makes a new array and leaves the original alone. sort sorts a var array in place: var v = [3, 1, 2] followed by v.sort { $0 > $1 } leaves v as [3, 2, 1].
    • Use strict comparisons. The closure answers "must the first go before the second?". For two equal items the answer is no, so use < and >, not <= and >=.
    • last is optional. "Rex".last is a Character?, because an empty string has no last letter. The ! in a.last! takes the value out. It is safe here, because no name is empty; on an empty string it would crash.

    The lesson's exercise

    1. Prices from high to low. prices.sorted { $0 > $1 } turns [12, 5, 30, 8] into [30, 12, 8, 5].
    2. Names by last letter. The closure names its items a, b in. First rule: if a.last! and b.last! differ, return a.last! < b.last!, so the name whose last letter comes earlier in the alphabet goes first.
    3. Tie-break. Names with the same last letter go from A to Z: return a < b. "Max" and "Rex" both end in x, and the second rule puts Max first.
    class Solution {
        func mainFunction() {
            let prices = [12, 5, 30, 8]
            let names = ["Rex", "Luna", "Bella", "Max", "Bo", "Coco"]
            let highFirst = prices.sorted { $0 > $1 }
            for price in highFirst {
                print(price)
            }
            let byLastLetter = names.sorted { a, b in
                if a.last! != b.last! {
                    return a.last! < b.last!
                }
                return a < b
            }
            for name in byLastLetter {
                print(name)
            }
        }
    }
    

    It prints the prices 30, 12, 8 and 5, one per line, then the names:

    Bella
    Luna
    Bo
    Coco
    Max
    Rex
    

    Bella and Luna end in a, Bo and Coco in o, and Max and Rex in x. Within each group the names are A to Z by the second rule: Bella before Luna, and Bo before Coco.

    Key takeaways

    • The closure for sorted returns true when the first item goes first.
    • For two rules, use a, b in, check the first rule with an if, and return the second rule for ties.
    • Strict < and > only, never <=.
    • sorted returns a new array; sort changes a var in place.
    Keep going with Swift500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free