Problem overview
The function leaderboard receives an array of players. Each player is a tuple (name: String, score: Int). It returns only the names, from the highest score to the lowest. Players with the same score go in alphabetical order of their names. Names are unique.
For [("Ava", 30), ("Ben", 25), ("Cy", 40)] the result is ["Cy", "Ava", "Ben"]. For [("Mia", 50), ("Leo", 70), ("Ava", 50)] it is ["Leo", "Ava", "Mia"]: Leo leads, and Ava and Mia tie on 50, so Ava comes first.
Approach
There are two jobs: put the players in order, then keep only the names. Do them in that order, because the names alone no longer carry the scores.
The sorting uses a closure with two rules, as in the previous sorting lesson. The closure receives two players, a and b, and returns true when a must come first. The first rule compares scores, and a higher score goes first. The second rule is the tie-breaker, and it only matters when the scores are equal: the name that comes first from A to Z goes first.
After sorting, a loop walks over the ordered players and collects each name in a new array.
The tie in the second example shows why the order of the two rules matters. Leo (70) is compared with Mia (50) and Ava (50): the scores differ, so the first rule decides and Leo goes first. Mia and Ava have equal scores, so the first rule says nothing and the closure falls through to the second rule, which compares the names. If the name rule came first, the scores would never be used for players with different names, and since names are unique, that is every pair.
Step by step
- Sort with
players.sortedand a closure that names its itemsa, b in. - First rule: if
a.score != b.score, returna.score > b.score. The comparison is>because a bigger score goes first. - Second rule: for equal scores, return
a.name < b.name. Here<is right, because A to Z means the smaller string first. - Create an empty
namesarray withvar names: [String] = []. The type annotation is needed, since an empty literal gives Swift nothing to infer the element type from. - Loop over
orderedwithfor player in ordered. - Append
player.nametonames. The result holds names only. - Return
namesafter the loop, once every name is in.
The solution in Swift
class Solution {
func leaderboard(_ players: [(name: String, score: Int)]) -> [String] {
let ordered = players.sorted { a, b in
if a.score != b.score {
return a.score > b.score
}
return a.name < b.name
}
var names: [String] = []
for player in ordered {
names.append(player.name)
}
return names
}
}
Swift details:
- The labels give readable closures. Because the parameter type is
[(name: String, score: Int)], the closure can writea.scoreandb.nameinstead of$0.1and$1.0. sortedreturns a new array of tuples, andplayersis unchanged.- Two comparison directions in one closure. Scores go high to low (
>) and names go A to Z (<). That is normal: each rule has its own direction.
Edge cases and common mistakes
- One player.
[("Zoe", 0)]gives["Zoe"]. - Forgetting the tie-breaker. Sorting by score alone leaves tied players in whatever order the sort produces. For the second example, a closure that compares only scores returns
["Leo", "Mia", "Ava"], but the expected answer has"Ava"before"Mia". - Using
<for scores. That gives the lowest score first. The second example would start with Ava or Mia instead of Leo. - Returning the sorted pairs. The function returns
[String], so the scores must be dropped.
Testing
[("Ava", 30), ("Ben", 25), ("Cy", 40)]gives["Cy", "Ava", "Ben"].[("Mia", 50), ("Leo", 70), ("Ava", 50)]gives["Leo", "Ava", "Mia"].[("Zoe", 0)]gives["Zoe"].
