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. sortedversussort.sortedmakes a new array and leaves the original alone.sortsorts avararray in place:var v = [3, 1, 2]followed byv.sort { $0 > $1 }leavesvas[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>=. lastis optional."Rex".lastis aCharacter?, because an empty string has no last letter. The!ina.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
- Prices from high to low.
prices.sorted { $0 > $1 }turns[12, 5, 30, 8]into[30, 12, 8, 5]. - Names by last letter. The closure names its items
a, b in. First rule: ifa.last!andb.last!differ, returna.last! < b.last!, so the name whose last letter comes earlier in the alphabet goes first. - 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
sortedreturnstruewhen the first item goes first. - For two rules, use
a, b in, check the first rule with anif, and return the second rule for ties. - Strict
<and>only, never<=. sortedreturns a new array;sortchanges avarin place.
