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 while points come in, and it can start over. Its methods are:
ScoreKeeper::new()makes a keeper with no points yet.add(points)records the points of 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, sototal()andbest()both return 0 until the nextadd.
For example, after add(10), add(25) and add(5), the total is 40 and the best turn is 25. After a reset, the old 25 no longer counts: if add(20) comes next, the best is 20.
Approach
One way is to store every turn in a vector and compute the sum and the maximum on request. That works, but each call to total or best would walk the whole list. There is a simpler way: keep only two numbers and update them as each turn arrives.
- The running total grows by the points of every
add. - The best turn changes only when a turn beats it. It can't be worked out from the total later, so it gets a field of its own.
Since scores are never negative (0 to 1000 points per turn), 0 is a safe starting value for the best: it is exactly what best() must return before any add. The fields are named total_points and best_points, so they differ from the methods total and best. Rust would allow the same names, because fields and methods live apart, but different names make it easier to see which one you mean.
Step by step
- Declare two
i32fields:total_pointsandbest_points. - Start both at 0 in
new, with a struct literal as the last expression. - In
add, update the total first with+=. The method takes&mut self, a mutable borrow, because it changes the fields. A method that only reads takes&self. - Then update the best. If
pointsis greater thanbest_points, store it. This is the "largest so far" idea from looking for the maximum of a list. An equal score changes nothing, and the number would be the same anyway. totalandbestonly report their fields.adddid the work.resetsets both fields back to 0, the values a new keeper starts with.
The solution in Rust
struct ScoreKeeper {
total_points: i32,
best_points: i32,
}
impl ScoreKeeper {
fn new() -> ScoreKeeper {
ScoreKeeper { total_points: 0, best_points: 0 }
}
fn add(&mut self, points: i32) {
self.total_points += points;
if points > self.best_points {
self.best_points = points;
}
}
fn total(&self) -> i32 {
self.total_points
}
fn best(&self) -> i32 {
self.best_points
}
fn reset(&mut self) {
self.total_points = 0;
self.best_points = 0;
}
}
Here it is in use:
let mut k = ScoreKeeper::new();
println!("start: {} {}", k.total(), k.best());
k.add(30);
k.add(50);
println!("{} {}", k.total(), k.best());
k.reset();
println!("{} {}", k.total(), k.best());
k.add(20);
println!("{} {}", k.best(), k.total());
It prints:
start: 0 0
80 50
0 0
20 20
Rust details worth knowing:
let mutis required. Callingaddorresetneeds a mutable borrow, so the variable must be declaredmut. Withlet k = ScoreKeeper::new();, the callk.add(5)fails to compile with "cannot borrowkas mutable, as it is not declared as mutable".- The methods that return a value end in an expression.
self.total_pointshas no semicolon, so it is the return value.addandresetreturn nothing, so every line ends with a semicolon. - Each keeper is separate. Two keepers made with
ScoreKeeper::new()each have their own two fields.
Complexity
Every method runs in O(1) time, because each does a fixed number of operations on two numbers. The struct uses O(1) space: two integers, however many turns were added. The vector idea would use O(n) space for n turns, and its total and best would take O(n) time.
Edge cases and common mistakes
- Asking before any
add. Bothtotal()andbest()return 0, because the fields start at 0. - A turn worth 0 points. It changes neither number: the total grows by 0 and 0 is not greater than the best. The keeper still counts it as an
add, but with scores of 0 or more nothing visible changes. - Resetting only one field. If
resetzeroes onlytotal_points, a keeper that had a best of 50 still reports a best of 50 after the reset. Both fields must go back to 0, so that afteradd(20)the best is 20, not 50. - Using
>=or>for the best. Either gives the right number, since an equal score is the same value. - Using
&selfinadd. The compiler reports "cannot assign toself.total_points, which is behind a&reference", soaddandresetneed&mut self.
Testing
- Adding 10, 25 and 5, then asking
total()andbest(), gives40 25. - On a new keeper,
best()andtotal()give0 0. Afteradd(0)they still give0 0. - Adding 30 and 50 gives
80 50. Afterreset()the answers are0 0, and afteradd(20)bothbest()andtotal()return 20.
Try the same sequences in the editor above before you check your answer.
