Problem overview
leaderboard gets an ArrayBuffer[(String, Int)] called players. Each player is a pair of a name and a score, like ("Ava", 30). It returns the names only, from the highest score to the lowest. Players with the same score go in alphabetical order of their names. No two players share a name, and scores run from 0 to 1000.
[("Ava", 30), ("Ben", 25), ("Cy", 40)]gives["Cy", "Ava", "Ben"].[("Mia", 50), ("Leo", 70), ("Ava", 50)]gives["Leo", "Ava", "Mia"]: Ava and Mia both have 50, and Ava comes first.[("Zoe", 0)]gives["Zoe"].
Approach
There are two orders to combine: scores from high to low, and names from A to Z. sortBy sorts from small to large, so a key of (score, name) would put low scores first. The trick is to negate the score. A score of 70 becomes -70, which is smaller than -50, so the highest score sorts first. The name stays as it is, so ties go from A to Z.
The key is the tuple (-player._2, player._1). Tuples compare position by position: the negated score first, and the name only when the scores are equal. Negation works because scores are whole numbers from 0 to 1000, far from the limits of Int.
After sorting, the pairs are in the right order. The answer needs only the names, so a loop copies them out in that order.
The keys for the second example
Here is the key sortBy builds for each player in [("Mia", 50), ("Leo", 70), ("Ava", 50)]:
Mia -> (-50,Mia)
Leo -> (-70,Leo)
Ava -> (-50,Ava)
-70 is the smallest key, so Leo comes first. The other two share -50, so their names decide, and Ava comes before Mia. The sorted pairs are (Leo,70), (Ava,50), (Mia,50), and the loop turns them into the names.
Step by step
- Make
orderedwithplayers.sortBy(player => (-player._2, player._1)). - Create an empty
ArrayBuffer[String]namednames. - Loop over
orderedand unpack each pair into(name, score). - Inside the loop, add
nametonameswith+=. - After the loop, return
names.
The solution in Scala
class Solution {
def leaderboard(players: ArrayBuffer[(String, Int)]): ArrayBuffer[String] = {
val ordered = players.sortBy(player => (-player._2, player._1))
val names = ArrayBuffer[String]()
for ((name, score) <- ordered) {
names += name
}
return names
}
}
Notes on the Scala:
player._2is the score andplayer._1is the name. The pair is not changed bysortBy, only moved.- In
for ((name, score) <- ordered)the pair is unpacked as inval (name, score) = pair. The loop never usesscore, which is fine in Scala. names += nameadds to the end of the buffer.
Another way
The names can be extracted with map instead of a loop: players.sortBy(player => (-player._2, player._1)).map(p => p._1) gives ArrayBuffer(Leo, Ava, Mia) for the second example. Another option is sortWith, which takes a function that says whether the first item goes before the second: players.sortWith((a, b) => if (a._2 != b._2) a._2 > b._2 else a._1 < b._1). It gives the same order, but the tuple key says the same thing in one short expression. The lesson's version uses the loop so you practice unpacking a pair.
Testing
[("Ava", 30), ("Ben", 25), ("Cy", 40)] ArrayBuffer(Cy, Ava, Ben)
[("Mia", 50), ("Leo", 70), ("Ava", 50)] ArrayBuffer(Leo, Ava, Mia)
[("Zoe", 0)] ArrayBuffer(Zoe)
Common mistakes
- Forgetting the minus sign. With the key
(player._2, player._1), the list sorts from the lowest score up. ForAva 30, Ben 30, Cy 40that givesArrayBuffer(Ava, Ben, Cy), but the right answer isCy, Ava, Ben. - Negating the name. You can't put a minus sign on a string. Only the number is negated.
- Returning pairs. The function returns names, so the loop copies only
name.
