Problem overview
A game shows each player two numbers: the total of all their turns, and their best single turn. The class ScoreKeeper keeps both up to date as points come in. It has four methods:
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 points of a singleaddin that time, or 0 if there was none.reset()starts over, so thattotal()andbest()both return 0 until the nextadd.
After add(10), add(25) and add(5), the total is 40 and the best is 25. Points are between 0 and 1000, and a test has at most 300 operations.
Approach
One way is to store every turn in a list and compute the sum and the maximum whenever they are asked for. That works, but it keeps data the class never needs and does more work with every question. A simpler idea is to keep only two numbers and update them as each turn arrives. The total grows by points every time. The best changes only when a bigger score shows up, which is the same idea as finding the largest value of a list one value at a time.
With two fields, every method takes constant time: add does one addition and one comparison, and the other three methods just read or set the fields. The total and best never need to look back at old turns.
Step by step
- Declare
totalPoints, anintthat starts at 0. A field can start with a value, written like a variable's, so every new keeper gets its own zero and the class needs no constructor. - Declare
bestPointsthe same way. Before anyadd,best()must return 0, and every score is at least 0, so 0 is a safe start. - In
add, add the points to the total with+=. - Update the best. If
pointsis greater thanbestPoints, store it. With>, an equal score leaves the best as it is, which is the same number anyway. total()andbest()return the two fields. They only report:addkeeps the fields current.reset()sets both fields back to 0, the values a new keeper starts with. After a reset the best counts only the adds that come later.
The solution in C++
class ScoreKeeper {
public:
int totalPoints = 0;
int bestPoints = 0;
void add(int points) {
totalPoints += points;
if (points > bestPoints) {
bestPoints = points;
}
}
int total() {
return totalPoints;
}
int best() {
return bestPoints;
}
void reset() {
totalPoints = 0;
bestPoints = 0;
}
};
The field names differ from the method names on purpose. A class can't have a data member and a member function with the same name in C++, so a field called total next to a method total() would not compile.
A run
This is the third example: two turns, a reset, then a new turn.
#include <iostream>
using namespace std;
// the class above goes here
int main() {
ScoreKeeper s;
s.add(30);
s.add(50);
cout << s.total() << " " << s.best() << endl;
s.reset();
cout << s.total() << " " << s.best() << endl;
s.add(20);
cout << s.best() << " " << s.total() << endl;
}
It prints:
80 50
0 0
20 20
The 50 from before the reset no longer counts, so after add(20) the best is 20, not 50. A keeper that has never received an add reports zeros:
#include <iostream>
using namespace std;
// the class above goes here
int main() {
ScoreKeeper s;
cout << s.best() << " " << s.total() << endl;
}
It prints:
0 0
Two keepers at once
Every object has its own fields, so two keepers never share a total. In this run, the first keeper gets two turns and the second gets one:
#include <iostream>
using namespace std;
// the class above goes here
int main() {
ScoreKeeper first;
ScoreKeeper second;
first.add(7);
first.add(3);
second.add(1);
cout << first.total() << " " << first.best() << endl;
cout << second.total() << " " << second.best() << endl;
first.reset();
cout << first.total() << " " << second.total() << endl;
}
It prints:
10 7
1 1
0 1
After first.reset(), the first keeper's total is 0 while the second keeper's total is still 1. The second keeper is untouched by the first one's adds and by its reset. That is what makes a class useful: the same code, many independent sets of values.
Details worth knowing
- Default member values. Writing
int totalPoints = 0;inside the class gives every object that starting value. Without it, anintfield of an object created asScoreKeeper s;holds an indeterminate value, and reading it is undefined behaviour. - Each object has its own fields. Two keepers never share totals: adding to one leaves the other alone.
- A turn worth 0.
add(0)is allowed. It changes neither the total nor the best, because 0 is not greater than the best, which is at least 0.
Common mistakes
- Forgetting to reset
bestPointsinreset(), so the old best survives. Resetting only the total is the usual slip. - Updating the best with
bestPoints = pointsfor every turn, which turns it into the last turn instead of the highest. - Comparing
pointswithtotalPointsinstead ofbestPoints. The best is about single turns, so it must be compared with the best turn so far, not with the sum of all turns. - Starting
bestPointsat a large number such as 1000: no turn would ever beat it. Starting it at 0 is right because points are never negative. If scores could be negative, 0 would be wrong and you would need to remember whether anyaddhad happened.
