Problem overview
A game shows each player two numbers: the total of all the points of their turns and their best single turn. The class ScoreKeeper keeps both up to date as turns come in:
add(_ points: Int)records one turn;total()returns the sum of all points added since the keeper was created or last reset;best()returns the highest singleaddin that time, or 0 if there was none;reset()starts over, so thattotal()andbest()are 0 until the nextadd.
After add(10), add(25), add(5), the total is 40 and the best is 25.
Approach
The keeper never needs to remember the individual turns. Two numbers are enough: the running total and the best so far. Each add updates both, so total() and best() only report them. Every method is O(1).
The best starts at 0 because points are never negative: the first turn is always at least 0, so it can't be hidden by the start value, and "no add yet" correctly reports 0.
Step by step
- Declare
totalPointswithvar, typeInt, starting at 0. - Declare
bestPointsthe same way. The property names differ from the method namestotalandbest, because a property and a method can't share a name in the same class. - In
add, grow the total:totalPoints += points, whatever the points are. - Then update the best: if
pointsis greater thanbestPoints, store it. This is the "largest so far" idea applied one value at a time. An equal score changes nothing, which is fine because it is the same number. total()andbest()return the two properties.reset()sets both properties back to 0, the values a new keeper starts with.
The solution in Swift
class ScoreKeeper {
var totalPoints: Int = 0
var bestPoints: Int = 0
func add(_ points: Int) {
totalPoints += points
if points > bestPoints {
bestPoints = points
}
}
func total() -> Int {
return totalPoints
}
func best() -> Int {
return bestPoints
}
func reset() {
totalPoints = 0
bestPoints = 0
}
}
Here it is in use:
let k = ScoreKeeper()
print(k.best(), k.total())
k.add(30)
k.add(50)
print(k.total(), k.best())
k.reset()
print(k.total(), k.best())
k.add(20)
print(k.best(), k.total())
0 0
80 50
0 0
20 20
After the reset, the 50 no longer counts: the best after add(20) is 20.
Swift details worth knowing
- Default values replace
init. Both properties have a starting value, so the class needs no initializer:ScoreKeeper()works as it is, and every keeper gets its own pair of numbers. _inadd(_ points: Int)means the caller writes no label:k.add(30), notk.add(points: 30).- Methods can change properties of a class without any special keyword. (A
structwould needmutatingonaddandreset.)
Each keeper has its own numbers
The two properties belong to the object, not to the class. If you create a second keeper and call add(7) on it, the first keeper's total doesn't move: after the run above, the first keeper still has a total of 20 while the new one has 7. That is what "one new ScoreKeeper" means in the lesson's tests, and why the properties are declared inside the class body without static.
Why reset sets the best to 0
It would be tempting to think of reset() as "forget the total". But the best is also history, and a keeper that has been reset should behave like a brand new one. The simplest way to guarantee that is for reset() to assign the same two starting values that the property declarations use.
Edge cases and common mistakes
- Asking before any
add. Both answers are 0, as the first line of the run shows. - A turn worth 0.
add(0)changes neither number. - Forgetting
bestPoints = 0inreset. The total would restart, but an old best of 50 would survive andbest()would wrongly return 50 afteradd(20). - Naming a property
total. It clashes with the methodtotal(); pick a different property name. - Recomputing instead of remembering. Storing every turn in an array would work but needs more memory and more time per question; two numbers do the job.
Complexity
Every method does a fixed number of steps: one addition and one comparison in add, a single read in total() and best(), two assignments in reset(). So each call is O(1) time, however many turns have been added, and the class uses O(1) space: two integers.
Testing
These sequences are worth trying in a file of your own, with the answers each call must give:
add(10),add(25),add(5), thentotal()andbest(): 40 and 25.best()andtotal()on a brand new keeper: 0 and 0.add(30),add(50),reset(), thentotal()andbest(): 0 and 0.- After that,
add(20), thenbest()andtotal(): 20 and 20.
The same sequences, run on the solution above, print exactly these numbers.
Key takeaways
- When a question asks for a running summary, store the summary and update it on every event, instead of storing the events.
- Each object has its own copy of its properties.
- A
resetmethod should put the object back into the same state a new one starts in.
