Problem overview
ScoreKeeper follows one player's score. Add(points) records one turn, Total() returns the sum of all turns since the start or the last reset, and Best() returns the highest single turn in that time. Reset() starts over. Before any Add, both Total() and Best() return 0.
After adding 10, 25 and 5, the total is 40 and the best is 25. After a Reset, an earlier 50 no longer counts: add 20 and the best is 20.
Approach
The tempting design is to store every turn in a list and compute the total and the best on request. That works, but it uses memory for turns nobody needs again, and each request has to look through the list.
Two numbers are enough. Keep the running total and the best turn so far, and update both inside Add. Then Total and Best only report a field, and every method is O(1). The best so far changes only when a bigger score arrives, the same idea as finding the largest value in a list one value at a time.
Step by step
- Declare
totalPointsas apublic intthat starts at 0. A field can be given a starting value like a variable, and every new keeper gets its own copy, so the class needs no constructor. - Declare
bestPointsthe same way. Every score is at least 0, so 0 is a safe start and is whatBest()must return before anyAdd. - In
Add, addpointstototalPointswith+=. The total grows with every turn. - Update the best: if
pointsis greater thanbestPoints, store it. With>, an equal score leaves the best unchanged, which is the same number anyway. TotalandBestreturn their fields.Resetsets both fields back to 0, the values of a new keeper.
The fields are named totalPoints and bestPoints because a class can't have a field and a method with the same name: a field called Total would clash with the method Total().
The solution in C#
class ScoreKeeper {
public int totalPoints = 0;
public int bestPoints = 0;
public void Add(int points) {
totalPoints += points;
if (points > bestPoints) {
bestPoints = points;
}
}
public int Total() {
return totalPoints;
}
public int Best() {
return bestPoints;
}
public void Reset() {
totalPoints = 0;
bestPoints = 0;
}
}
Using it:
var k = new ScoreKeeper();
Console.WriteLine(k.Total() + " " + k.Best());
k.Add(10); k.Add(25); k.Add(5);
Console.WriteLine(k.Total() + " " + k.Best());
k.Reset(); k.Add(20);
Console.WriteLine(k.Total() + " " + k.Best());
This prints:
0 0
40 25
20 20
Edge cases and common mistakes
- No adds yet. Both methods return 0, because the fields start at 0.
- A turn worth 0.
Add(0)changes neither number. - Forgetting to reset the best. If
Resetclears only the total, the old best survives. AfterAdd(50),Reset()andAdd(20), the best must be 20. - Assigning instead of adding.
totalPoints = pointskeeps only the last turn;+=accumulates. - Calling the field and the method the same. Keep the field names different from the method names.
Why store two numbers instead of a list
A class is useful when something has a memory. Each ScoreKeeper object carries its own two numbers between calls, so Add can change them and Total can read them later. Nothing is passed back and forth: the object is the memory.
Keeping a list of turns would not be wrong, but it would make Total and Best walk the whole list on every call. The statement allows up to 300 operations, so that would still be fast here, but the two-number design gives the same answers with constant work and no growing memory, and it shows a habit worth keeping: store what you will need to answer, not everything you have seen.
Testing
Try these call sequences on a new keeper and compare:
Add(10),Add(25),Add(5):Total()is 40 andBest()is 25.- No calls:
Best()andTotal()are both 0. AfterAdd(0)they are still 0. Add(30),Add(50): total 80, best 50. ThenReset(): both 0. ThenAdd(20): best 20 and total 20, not 50.
Other ways to write it, and separate keepers
The "keep the larger one" step can use Math.Max instead of an if. This version of Add behaves the same:
public void Add(int points) {
totalPoints += points;
bestPoints = Math.Max(bestPoints, points);
}
Math.Max needs using System; at the top of a file of your own.
Each object has its own numbers. With two keepers a and b, after a.Add(7), b.Add(2), b.Add(9) and a.Add(3), the keeper a has total 10 and best 7, while b has total 11 and best 9. Resetting one does not touch the other.
Mistakes worth recognising
- Computing in the getters. If
Totaltried to recalculate anything, it would need data the object no longer has. Do the work inAdd, where each turn is available once. - Starting
bestPointstoo high. Starting at a large number such as 1000 would make every real turn look worse than the best. Starting at 0 is right because scores are never negative. - Starting too low for other data. If scores could be negative, 0 would be a wrong start, and the best would need to be unset until the first
Add. The lesson's scores are between 0 and 1000, so 0 works.
Key takeaways
- An object keeps its state in fields between method calls.
- Update the state when new data arrives, so reading it is instant.
Resetreturns the fields to the values a new object starts with.
