Start learning free

LESSON 252 OF 572 · FUNCTIONS & CLASSES

Score Keeper in C#: Keeping State in a Class

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 7
    Ctrl + Enter to check

    C# · FUNCTIONS & CLASSES

    Score Keeper in C#, explained

    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

    1. Declare totalPoints as a public int that 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.
    2. Declare bestPoints the same way. Every score is at least 0, so 0 is a safe start and is what Best() must return before any Add.
    3. In Add, add points to totalPoints with +=. The total grows with every turn.
    4. Update the best: if points is greater than bestPoints, store it. With >, an equal score leaves the best unchanged, which is the same number anyway.
    5. Total and Best return their fields.
    6. Reset sets 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 Reset clears only the total, the old best survives. After Add(50), Reset() and Add(20), the best must be 20.
    • Assigning instead of adding. totalPoints = points keeps 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 and Best() is 25.
    • No calls: Best() and Total() are both 0. After Add(0) they are still 0.
    • Add(30), Add(50): total 80, best 50. Then Reset(): both 0. Then Add(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 Total tried to recalculate anything, it would need data the object no longer has. Do the work in Add, where each turn is available once.
    • Starting bestPoints too 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.
    • Reset returns the fields to the values a new object starts with.
    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free