Problem overview
A bottle holds at most a fixed amount of water: you can't pour in more than fits, and you can't drink more than is in it. The class WaterBottle tracks one bottle, with amounts in whole milliliters:
WaterBottle(capacity:)creates an empty bottle that holds at mostcapacity;fill(_ amount:)pours in as much as fits and returns how much actually went in;drink(_ amount:)drinks as much as is there and returns how much was actually drunk;level()returns how much water is in the bottle.
With capacity 300, fill(250) returns 250. Then fill(100) has room for only 50, so it returns 50 and the bottle is full. A full bottle takes nothing more: fill(10) returns 0.
Approach
The bottle needs two numbers: its capacity, which never changes, and the water it holds now. Both limits have the same shape, so both methods use min:
- filling adds the smaller of the amount and the room left,
capacity - water; - drinking removes the smaller of the amount and the water present.
The water then always stays between 0 and the capacity, and the return value tells the caller what really happened. Each method does a few operations on numbers, so each is O(1).
Step by step
- Declare
capacityas anIntproperty.fillneeds it to know how much still fits. - Declare
waterstarting at 0, because a new bottle is empty. It isn't namedlevel, since a property and a method can't share a name. - In
init, store the capacity:self.capacity = capacity.wateralready has its starting value. - In
fill, declareaddedasmin(amount, capacity - water). - Add
addedtowater, notamount, so the bottle never overflows. - Return
added. - In
drink, declaredrunkasmin(amount, water), subtract it fromwater, and return it. level()returnswater.
The solution in Swift
class WaterBottle {
var capacity: Int
var water: Int = 0
init(capacity: Int) {
self.capacity = capacity
}
func fill(_ amount: Int) -> Int {
let added = min(amount, capacity - water)
water += added
return added
}
func drink(_ amount: Int) -> Int {
let drunk = min(amount, water)
water -= drunk
return drunk
}
func level() -> Int {
return water
}
}
A run on a bottle of capacity 300:
let w = WaterBottle(capacity: 300)
print(w.fill(250))
print(w.fill(100))
print(w.level())
print(w.fill(10))
print(w.drink(120))
print(w.level())
print(w.drink(500))
print(w.level())
250
50
300
0
120
180
180
0
The last drink(500) returns 180, not 500: only 180 was in the bottle, and the bottle is now empty.
Swift details worth knowing
minis a global function that returns the smaller of two values of the same type. Both arguments here areInt.letinside a method makesaddedanddrunkconstants: they are computed once and never change.- Only
capacityis set ininit. A property with a default value (= 0) doesn't need an assignment there.
Why not an if?
You could also write fill with an if: if the amount is more than the room left, pour only the room left, otherwise pour all of it. That is exactly what min does in one line. Using min removes a branch you could get wrong, and it makes the two methods mirror each other: the same shape, with a different limit.
What goes wrong without the limit
A version of fill that simply does water += amount and returns amount compiles and looks fine for small amounts. With capacity 300, calling fill(250), then fill(100), then level() prints 250, 100 and 350: a bottle of 300 holding 350. The lesson's tests catch this because they check both the returned amounts and the level.
Edge cases and common mistakes
- Adding
amountinstead ofadded. The bottle would go above its capacity. - Returning
amount. The methods must return what really happened:fill(100)into a bottle with room for 50 returns 50. - A full bottle. The room left is 0, so
fillreturns 0. - An empty bottle.
drinkreturns 0 and the level stays at 0. A drink of 0 changes nothing either. - Two limits mixed up.
fillis limited by the room left (capacity - water),drinkby the water present, not by the capacity.
Negative and oversized amounts
The lesson only gives amounts from 0 up to 10,000, so a negative amount never reaches the methods. If it did, min would pick that negative number and fill would drain the bottle. Real code would check for it. Here the constraints let the two min lines stay as simple as they are.
An amount larger than the capacity is fine: fill(10000) on an empty bottle of 300 adds 300 and returns 300.
Complexity
Every method does a constant number of arithmetic operations on two integers, so each one runs in O(1) time. The class stores two integers, so it uses O(1) space, no matter how many times you fill and drink.
Testing
A bottle of capacity 100, in a file of your own, is a good small case:
drink(10)on the empty bottle returns 0.fill(60)returns 60.drink(100)returns 60, because only 60 were there.level()returns 0, and a furtherdrink(5)returns 0.
Run in that order, the five calls print 0, 60, 60, 0 and 0.
