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. Its methods are add(points) to record a turn, total() and best() to report the numbers, and reset() to start over.
After add(10), add(25) and add(5), the total is 40 and the best is 25. A new keeper, before any add, reports 0 for both. After reset() the old turns no longer count: with a best of 50, a reset and then add(20), the best is 20, not 50.
Approach
The keeper never needs the list of turns. It is enough to keep two numbers and update them as each turn arrives: a running sum, and the highest value seen so far. That is the same idea as finding the largest value in a list, one value at a time. Because nothing is looked up or re-added, every method runs in O(1) time.
Points are never negative (the constraint is 0 to 1000), so 0 is a safe starting value for the best: any turn is at least as large.
Step by step
- Two fields. In the constructor, set
this.totalPointsandthis.bestPointsto 0. - Choose the names with care. The fields can't be called
totalandbest: a property set in the constructor lives on the object itself, so it hides the method of the same name. - Add. Add
pointstothis.totalPointswith+=. Then, ifpointsis greater thanthis.bestPoints, store it as the new best. - Report.
total()returnsthis.totalPointsandbest()returnsthis.bestPoints. They only read whataddhas kept. - Reset. Set both fields back to 0, the values a new keeper starts with.
The solution in JavaScript
class ScoreKeeper {
constructor() {
this.totalPoints = 0;
this.bestPoints = 0;
}
add(points) {
this.totalPoints += points;
if (points > this.bestPoints) {
this.bestPoints = points;
}
}
total() {
return this.totalPoints;
}
best() {
return this.bestPoints;
}
reset() {
this.totalPoints = 0;
this.bestPoints = 0;
}
}
Using the class
let k = new ScoreKeeper();
k.add(10);
k.add(25);
k.add(5);
console.log(k.total(), k.best());
k = new ScoreKeeper();
console.log(k.best(), k.total());
k.add(0);
console.log(k.best(), k.total());
k = new ScoreKeeper();
k.add(30);
k.add(50);
console.log(k.total(), k.best());
k.reset();
console.log(k.total(), k.best());
k.add(20);
console.log(k.best(), k.total());
This prints:
40 25
0 0
0 0
80 50
0 0
20 20
Common mistakes
- A field named like a method. With
this.total = 0in the constructor and a methodtotal()in the class, callingkeeper.total()fails withTypeError: keeper.total is not a function, because the property holding the number is found first. - Forgetting to reset the best. Resetting only the total leaves the old best in place, so after
reset()andadd(20)the best would still be 50. - Starting the best too high or too low. Starting it at 0 works because points are never negative. Without an
add,best()has to return 0. - Overwriting the total.
this.totalPoints = pointskeeps only the last turn; the total needs+=.
Why not keep every turn?
A first idea is to push each turn into an array and compute the total and the best when asked, with a loop over the array. That works, but then total() and best() take O(n) time for n turns, and the object grows with every add. Keeping two numbers instead costs a constant amount of memory and makes every method O(1). The price is that the keeper forgets the individual turns, which is fine because nobody asks for them.
A trace
Adding the turns 7, 7, 3 and 9 to a new keeper gives these values after each add:
| add | total() | best() |
|---|---|---|
| 7 | 7 | 7 |
| 7 | 14 | 7 |
| 3 | 17 | 7 |
| 9 | 26 | 9 |
The second 7 doesn't change the best: the comparison uses >, so an equal score leaves the best as it is, which is the same number anyway. The 3 adds to the total but is smaller than the best, so the best stays. After two reset() calls in a row, both methods still return 0: resetting a fresh keeper is harmless.
Edge cases
- Before any
add, bothtotal()andbest()return 0. add(0)changes neither number, since 0 is not greater than the starting best of 0.- After
reset(), the keeper behaves like a new one. The best only counts turns added later.
How the object keeps its state
Each new ScoreKeeper() creates its own object with its own totalPoints and bestPoints. Two keepers never share their numbers: adding points to one leaves the other untouched. The constructor runs once per object, which is why it is the right place to give the fields their first values.
The methods add and reset change the object and return nothing. total and best change nothing and return a value. Splitting the work this way keeps each method small: the code that updates the numbers is in one place, and the code that reports them just reads the fields.
Notice that reset() repeats the two assignments of the constructor. That is normal for a beginner class. Later, you can have the constructor call this.reset() to say it once, but both versions behave the same.
