Problem overview
Leaderboard gets a List<(string Name, int Score)> named players, pairs like ("Ava", 30). It returns a List<string> with only the names, from the highest score to the lowest. Players with equal scores are ordered alphabetically by name. No two players share a name.
For [("Mia", 50), ("Leo", 70), ("Ava", 50)] the result is ["Leo", "Ava", "Mia"]: Leo leads with 70, then Ava and Mia tie at 50 and Ava comes first.
Approach: sort the pairs, then keep the names
There are two jobs: ordering and picking out a part. Do them in that order, because the scores are needed to sort and are not needed afterwards.
OrderByDescending(p => p.Score)sorts from the biggest score to the smallest. The lambda receives one tuple, sop.Scorereads its named part.ThenBy(p => p.Name)orders players who tie on score from A to Z. Note the mix: descending for the score, ascending for the name.Select(p => p.Name)turns each pair into just its name and keeps the order.
Step by step
- Sort
playerswithOrderByDescendingon the score, thenThenByon the name, thenToList(). Store it inordered, declared withvar. - Select the names from
orderedwithSelectandToList(), and store them in aList<string>namednames. - Return
names.
The solution in C#
class Solution {
public List<string> Leaderboard(List<(string Name, int Score)> players) {
var ordered = players.OrderByDescending(p => p.Score).ThenBy(p => p.Name).ToList();
List<string> names = ordered.Select(p => p.Name).ToList();
return names;
}
}
With var, the compiler gives ordered the type List<(string Name, int Score)>, so p.Name and p.Score keep working in the next line. Writing that type out by hand would also be correct, just longer.
Another way
The two steps can be one chain, with no ordered variable:
players.OrderByDescending(p => p.Score).ThenBy(p => p.Name).Select(p => p.Name).ToList()
On [("Mia", 50), ("Leo", 70), ("Ava", 50)] this gives [Leo, Ava, Mia], the same as the lesson's code. The lesson splits it so each step can be checked on its own.
You can also unpack the pairs in a foreach over the sorted sequence, foreach (var (name, score) in sorted). For that example it visits Leo 70, Ava 50 and Mia 50, in that order.
Why the tie-break is a second key
You might try to sort first by name and then by score, hoping the name order survives. OrderBy and OrderByDescending are stable, meaning tied items keep their earlier order, so that can work, but the code would have to run the sorts in the reverse order from the rule: name first, score last. ThenBy states the rule in the order you say it aloud ("by score, then by name") and avoids that trap. For example, sorting the same players with only OrderBy(p => p.Score) gives [Mia, Ava, Leo], with the tied Mia and Ava left in their input order, not alphabetical.
Complexity
The sort is O(n log n) for n players. Select adds one pass, O(n). The lists use O(n) extra space.
Testing
Outputs from running the solution:
| players | Result |
|---|---|
[("Ava", 30), ("Ben", 25), ("Cy", 40)] | [Cy, Ava, Ben] |
[("Mia", 50), ("Leo", 70), ("Ava", 50)] | [Leo, Ava, Mia] |
[("Zoe", 0)] | [Zoe] |
Common mistakes
- Using
OrderByinstead ofOrderByDescending. That puts the lowest score first, the opposite of a leaderboard. - Using
OrderByDescendingtwice. A secondOrderByDescendingon the name would put the ties in Z-to-A order. And a second sort call, instead ofThenByorThenByDescending, replaces the first sort's order rather than refining it. - Selecting before sorting. Once the pairs have become plain names, the scores are gone and there is nothing left to sort by.
