Start learning free

LESSON 253 OF 573 · FUNCTIONS & CLASSES

Score Keeper in Go: A Struct with Pointer Receivers

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

    GO · FUNCTIONS & CLASSES

    Score Keeper in Go, explained

    Problem overview

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

    It has five parts: NewScoreKeeper() makes a keeper with no points, add(points) records one turn, total() returns the sum, best() returns the highest single turn (0 if there was none), and reset() starts over.

    After add(10), add(25) and add(5), total() is 40 and best() is 25.

    Approach

    You could store every turn in a list and work the answers out on request. That works, but it keeps growing and each call has to look through the list. The better idea is to keep only the two numbers the questions are about and update them as each turn arrives:

    • the total grows by the new points every time;
    • the best changes only when the new points are higher than the best so far.

    The best cannot be worked out from the total later on, which is why it needs its own field. With both fields kept current, total and best just report them. Every method runs in constant time.

    Starting the best at 0 is safe: points are never negative, so any first turn is at least 0, and a keeper with no turns must report a best of 0 anyway.

    Step by step

    1. Declare totalPoints, an int for the running sum. The field can't be called total, because a struct can't have a field and a method with the same name.
    2. Declare bestPoints, the highest single add so far.
    3. Write the constructor, setting both fields to 0. A new keeper has seen no points.
    4. In add, add the points to totalPoints. The method changes the struct, so its receiver is a pointer.
    5. Update the best. If points is greater than bestPoints, store it. With >, an equal score leaves the best as it is, which is the same number anyway.
    6. Write total and best. Each returns its field. They only read, so a value receiver is enough.
    7. Write reset. Set both fields back to 0, the values a new keeper has, so the best only counts adds that come later.

    The solution in Go

    type ScoreKeeper struct {
        totalPoints int
        bestPoints int
    }
    func NewScoreKeeper() ScoreKeeper {
        return ScoreKeeper{totalPoints: 0, bestPoints: 0}
    }
    func (this *ScoreKeeper) add(points int) {
        this.totalPoints += points
        if points > this.bestPoints {
            this.bestPoints = points
        }
    }
    func (this ScoreKeeper) total() int {
        return this.totalPoints
    }
    func (this ScoreKeeper) best() int {
        return this.bestPoints
    }
    func (this *ScoreKeeper) reset() {
        this.totalPoints = 0
        this.bestPoints = 0
    }
    

    Pointer receivers and value receivers

    This is the Go detail the lesson is about. A method with a value receiver, (this ScoreKeeper), works on a copy of the struct. Changes made to the copy vanish when the method returns. A method with a pointer receiver, (this *ScoreKeeper), works on the original, so its changes stay.

    That is why add and reset use pointers and total and best don't. If add had a value receiver, it would compile and run, but nothing would be recorded: after add(10) the field would still be 0.

    You don't have to write (&k).add(10). When k is a variable, k.add(10) takes its address for you.

    Why not store every turn

    Keeping a list of all turns would also give the right answers, but add would grow the list for ever, total would have to add the whole list up on each call, and best would have to scan it. Two integers carry all the information the two questions need. When a struct only has to answer a few questions, ask which facts those questions depend on and store just those.

    Using it

    k := NewScoreKeeper()
    fmt.Println(k.best(), k.total())
    k.add(30)
    k.add(50)
    fmt.Println(k.total(), k.best())
    k.reset()
    fmt.Println(k.total(), k.best())
    k.add(20)
    fmt.Println(k.best(), k.total())
    

    This prints:

    0 0
    80 50
    0 0
    20 20
    

    After the reset, the earlier 50 no longer counts, so the best after add(20) is 20, not 50. Two keepers are independent: after k2 := NewScoreKeeper() and k2.add(10), k.total() is still 20 while k2.total() is 10.

    What a value receiver does to add

    Suppose a struct BadKeeper with a field totalPoints has this method:

    func (this BadKeeper) add(p int) { this.totalPoints += p }
    

    After b := BadKeeper{} and b.add(10), printing b.totalPoints gives 0. The method added 10 to its own copy, and the copy was thrown away when the method returned. Go reports no error, so the only sign is a total that never moves.

    The fix is the pointer receiver. With (this *BadKeeper), the method receives the address of b and the 10 stays.

    Edge cases and common mistakes

    • A value receiver on add or reset. The code compiles, but the changes are lost on a copy.
    • Not resetting the best. A reset that clears only the total leaves the old best behind.
    • Calling best() before any add. It returns 0, because the field starts at 0.
    • A turn worth 0. add(0) changes neither number.
    • Naming a field like a method. A field total next to a method total() is a compile error, hence totalPoints and bestPoints.
    Keep going with Go500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free