Problem overview
A game shows each player two numbers: the total of all their turns, and their best single turn. ScoreKeeper keeps both up to date as points come in, and can start over.
The methods are:
add(points)records one turn;total()returns the sum of all points added since the keeper was created or last reset;best()returns the highest singleaddin that time, or 0 if there was none;reset()starts over, sototal()andbest()both return 0 until the nextadd.
After add(10), add(25) and add(5), the total is 40 and the best is 25.
Approach: update the two numbers as the points arrive
You could store every turn in a list and work out the total and the best when asked. That needs memory that grows with the number of turns, and every question has to look through the whole list. It is simpler to keep just two numbers inside the object and change them in add. Then total() and best() only report a field, and every method runs in O(1) time.
The object has to remember its numbers between calls, so they become fields of the class. They are vars, because add and reset change them. The statement asks for field names that differ from the methods' names, so the fields are totalPoints and bestPoints.
Step by step
- Declare
totalPointsinside the class, avarof typeIntstarting at 0. Every new keeper gets its own copy. - Declare
bestPointsthe same way. Before anyadd,best()must return 0, and points are never negative, so 0 is a safe starting value. - In
add, add the points tototalPointswith+=. The total grows on every turn. - Update the best. If
pointsis greater thanbestPoints, store it. This is the same idea as finding the largest value of a list one value at a time. An equal score leaves the best unchanged, which is the same number anyway. totalandbestjust return their fields.resetsets both fields back to 0, the values a new keeper starts with.
The solution in Scala
class ScoreKeeper {
var totalPoints: Int = 0
var bestPoints: Int = 0
def add(points: Int): Unit = {
totalPoints += points
if (points > bestPoints) {
bestPoints = points
}
}
def total(): Int = {
return totalPoints
}
def best(): Int = {
return bestPoints
}
def reset(): Unit = {
totalPoints = 0
bestPoints = 0
}
}
Scala details:
- Fields live in the class body. A
vardeclared there with an initial value is created for every new object, so two keepers never share their numbers. Unitmeans nothing useful is returned.addandresetare called for their effect, so their result type isUnit.- Calling with or without parentheses. The methods are declared with empty parentheses, so write
k.total(). Creating the object works asnew ScoreKeeperornew ScoreKeeper().
Using the class
val k = new ScoreKeeper
println(s"${k.best()} ${k.total()}")
k.add(30); k.add(50)
println(s"${k.total()} ${k.best()}")
k.reset()
println(s"${k.total()} ${k.best()}")
k.add(20)
println(s"${k.best()} ${k.total()}")
It prints:
0 0
80 50
0 0
20 20
A new keeper reports 0 and 0. After 30 and 50 the total is 80 and the best is 50. After reset() both are 0, and after add(20) the best is 20, not 50: the old 50 no longer counts.
A second keeper with add(10), add(25) and add(5) reports a total of 40 and a best of 25.
Why the fields belong to the object
Each call to add is a separate call, and local variables vanish when a method ends. Anything that has to survive from one call to the next must live in a field. The two vars are created when new ScoreKeeper runs and stay with that object until it is no longer used. Because each object has its own copy, two keepers can run side by side: one can hold a total of 40 while the other is brand new and holds 0.
The design also explains why add does the bookkeeping and the two getters do none. If the work were done in total() and best(), the object would need to remember every turn. Doing it in add keeps just the two numbers that the questions need.
Complexity
Every method does a constant amount of work: one addition, one comparison, or one field read. All of them run in O(1) time, and the object stores two integers.
Testing
Try these sequences on a new keeper each time, and compare with the results:
add(10),add(25),add(5), thentotal()andbest(): 40 and 25.best(),total(),add(0),best(),total(): all four answers are 0, since a turn worth 0 changes neither number.add(30),add(50),reset(),add(20), thenbest()andtotal(): 20 and 20.
The third case is the one that catches a reset that forgets the best: it would answer 50 for the best.
Common mistakes
- Not resetting the best. Setting only
totalPointsto 0 inresetleaves the old best behind. - Starting the best at a large number. It would never be beaten. Start at 0.
- Replacing the total with the new points. Use
+=, not=. - Adding 0 points. It changes neither number, and
add(0)on a new keeper leaves both at 0.
