Start learning free

LESSON 256 OF 576 · FUNCTIONS & CLASSES

Score Keeper in Rust: A Struct That Changes Its Own Fields

Practice O(1) Read the full explanation

A game shows each player two numbers: their total, all the points of their turns added up, and their best single turn. Write a class ScoreKeeper that keeps both numbers up to date as the points come in, and can start over.

Implement the class ScoreKeeper:

  • ScoreKeeper() creates a keeper with no points yet.
  • add(points) records the points of one turn.
  • total() returns the sum of all the points added since the keeper was created or last reset.
  • best() returns the highest points of a single add in that time, or 0 if there was no add yet.
  • reset() starts over: afterwards total() and best() both return 0 until the next add.

The input is a list of operations, applied in order to one new ScoreKeeper. The output lists what the total and best calls return, in order. The starter code spells these names the way your language does (in Go and Rust, ScoreKeeper is a struct with methods). Give the fields names that differ from the methods' names: in several languages a field named total would clash with the method total().

 

Example 1:

Input:
    add(10)
    add(25)
    add(5)
    total()
    best()
Output: 40 25
Explanation: 10 + 25 + 5 = 40. The best single turn was the 25.

Example 2:

Input:
    best()
    total()
    add(0)
    best()
    total()
Output: 0 0 0 0
Explanation: Before any add, both numbers are 0. A turn worth 0 points changes neither.

Example 3:

Input:
    add(30)
    add(50)
    total()
    best()
    reset()
    total()
    best()
    add(20)
    best()
    total()
Output: 80 50 0 0 20 20
Explanation: After reset() the keeper starts over. The 50 from before no longer counts,
so after add(20) the best is 20, not 50.

 

Constraints:

  • 0 <= points <= 1000
  • At most 300 operations on one ScoreKeeper.

Note:

Every method should run in O(1) time: add updates the two numbers as each turn comes in, so total and best never need to look back at old turns.

STEP 1 OF 8
    Ctrl + Enter to check

    RUST · FUNCTIONS & CLASSES

    Score Keeper in Rust, explained

    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 single add in that time, or 0 if there was none.
    • reset() starts over, so total() and best() both return 0 until the next add.

    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

    1. Declare two i32 fields: total_points and best_points.
    2. Start both at 0 in new, with a struct literal as the last expression.
    3. 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.
    4. Then update the best. If points is greater than best_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.
    5. total and best only report their fields. add did the work.
    6. reset sets 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 mut is required. Calling add or reset needs a mutable borrow, so the variable must be declared mut. With let k = ScoreKeeper::new();, the call k.add(5) fails to compile with "cannot borrow k as mutable, as it is not declared as mutable".
    • The methods that return a value end in an expression. self.total_points has no semicolon, so it is the return value. add and reset return 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. Both total() and best() 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 reset zeroes only total_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 after add(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 &self in add. The compiler reports "cannot assign to self.total_points, which is behind a & reference", so add and reset need &mut self.

    Testing

    • Adding 10, 25 and 5, then asking total() and best(), gives 40 25.
    • On a new keeper, best() and total() give 0 0. After add(0) they still give 0 0.
    • Adding 30 and 50 gives 80 50. After reset() the answers are 0 0, and after add(20) both best() and total() return 20.

    Try the same sequences in the editor above before you check your answer.

    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free