Problem overview
The function leaderboard receives a vector of players. Each player is a tuple (String, i32): a name and a score. It returns only the names, from the highest score to the lowest. Players with equal scores go in alphabetical order of their names, and no two players share a name.
For [("Ava", 30), ("Ben", 25), ("Cy", 40)] the answer is ["Cy", "Ava", "Ben"]. For [("Mia", 50), ("Leo", 70), ("Ava", 50)] it is ["Leo", "Ava", "Mia"]: Leo leads with 70, and Ava and Mia tie at 50, so Ava comes first.
Approach
Sorting by score ascending and then reversing would break the tie rule, because reversing the whole list also reverses the order of players with equal scores, so they would no longer be A to Z. Comparing the scores in the other direction avoids that.
Sort the pairs first, then pull out the names. The sort has two rules, and sort_by with a chained then expresses both: compare the scores in descending order, and use the names only when the scores are Equal.
To sort from high to low, swap the two sides of cmp: b.1.cmp(&a.1) instead of a.1.cmp(&b.1). For the names, the normal order a.0.cmp(&b.0) gives A to Z.
Step by step
- Sort the vector in place.
playersis declaredmut. In the closure,.1is the score and.0is the name of each tuple. - Score rule:
b.1.cmp(&a.1). Comparingbwithaputs the highest score first. - Name rule: chain
.then(a.0.cmp(&b.0)), which only counts when the scores are equal. - Create an empty vector
nameswithVec::new(), declaredlet mut. Rust infers its type from thepushcalls and the return type. - Loop over
playersitself, not&players. The loop takes each tuple out of the vector, so its name can be moved intonameswithout copying. - Push
player.0ontonames. The scores are left behind. - Return
namesafter the loop.
The solution in Rust
impl Solution {
fn leaderboard(mut players: Vec<(String, i32)>) -> Vec<String> {
players.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
let mut names = Vec::new();
for player in players {
names.push(player.0);
}
return names;
}
}
Because the loop consumes players, the vector can't be used after it. That is fine here, since the function no longer needs it. Looping over &players instead would need player.0.clone() to get an owned name.
Another way
A tuple of keys can do the same with std::cmp::Reverse, which flips the order of one field:
players.sort_by_key(|p| (Reverse(p.1), p.0.clone()));
Given [("Mia", 50), ("Leo", 70), ("Ava", 50)], this gives [("Leo", 70), ("Ava", 50), ("Mia", 50)] (with use std::cmp::Reverse; in a file of your own). The sort_by closure from the lesson says the same thing without an import or a clone.
Testing
The three examples give:
["Cy", "Ava", "Ben"]
["Leo", "Ava", "Mia"]
["Zoe"]
Edge cases and common mistakes
- Sorting scores ascending. Writing
a.1.cmp(&b.1)puts the lowest score first. - Flipping the name rule too. The names must stay A to Z, so only the score comparison is swapped.
- Looping over
&playersand pushingplayer.0. This tries to move aStringout of a borrowed tuple, and the compiler reports that it cannot move out of a shared reference. Useplayer.0.clone()there. - Returning the pairs. The function returns only names. Sorting the pairs is a step on the way, not the answer.
