Problem overview
The method topScorer gets scores, a mutable Map[String, Int] from player names to scores, and returns the name of the player with the highest score. When several players share the highest score, it returns the name that comes first alphabetically. The map has at least one player and every score is between 0 and 100.
For {"Ana": 90, "Ben": 75, "Cara": 88} the answer is Ana. For {"Zoe": 82, "Ana": 82} both players have 82, and Ana comes before Zoe. For {"Max": 0} the only player wins, even with 0.
Approach
Keep the best player seen so far, and compare every entry with it. This is a "best so far" scan, the same pattern as finding a maximum in a list, with a second rule for ties. A map has no fixed order, so the code must give the right answer whichever entry comes first. That is why the tie rule uses the names themselves and not the position in the loop.
Step by step
- Create two variables,
bestNameas an empty string andbestScoreas -1. They change during the loop, so they arevar. Scores are never below 0, so the first player always beats -1. - Loop with
for ((name, score) <- scores), which splits each entry into its key and value. - Inside, check whether this player is better: the score is higher, or the scores are equal and the name comes earlier. In Scala,
name < bestNamecompares two strings alphabetically. - If so, store both the name and the score as the new best.
- After the loop, return
bestName.
The solution in Scala
class Solution {
def topScorer(scores: scala.collection.mutable.Map[String, Int]): String = {
var bestName = ""
var bestScore = -1
for ((name, score) <- scores) {
if (score > bestScore || (score == bestScore && name < bestName)) {
bestName = name
bestScore = score
}
}
return bestName
}
}
The parentheses around score == bestScore && name < bestName make the tie rule one unit: it only counts when the two scores are equal. The tie rule never fires on the first entry, because bestScore starts at -1 and no score equals it.
Testing
The three examples give these results:
Ana -> 90, Ben -> 75, Cara -> 88returnsAna.Zoe -> 82, Ana -> 82returnsAna.Max -> 0returnsMax.
Another way
You can also sort the entries so that the best one comes first, then take it. Sorting by (-score, name) puts higher scores first and, among equal scores, earlier names first:
val top = scores.toSeq.sortBy { case (n, s) => (-s, n) }.head._1
For Zoe -> 82, Ana -> 82, Ben -> 70 this gives Ana. It works, but it sorts the whole map to find one entry. The single loop does the same job with one pass and no extra collection, and it shows the "best so far" pattern you will use again and again.
Edge cases and common mistakes
- Starting
bestScoreat 0. A lone player with 0 points would never beat it, and the method would return the empty string. Starting at -1 avoids that. - Using
>=alone. It would keep whichever tied player the loop visits last, and the order of a map is not fixed, so the answer would depend on luck. - Forgetting the parentheses around the tie check.
&&binds tighter than||, so this particular line would still work, but the parentheses make the intent clear. - Using
valfor the two best-so-far variables. They are reassigned, so the code would not compile.
