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 inside. The class WaterBottle keeps track of one bottle, in whole milliliters:
WaterBottle(capacity)creates an empty bottle.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 the water in the bottle now.
For a bottle of capacity 300, fill(250) returns 250. Then fill(100) has room for only 50 more, so it returns 50 and the bottle is full. The level is 300, and a further fill(10) returns 0.
Approach: ask for a lot, take what is possible
The bottle needs two numbers: its capacity, which never changes, and the water it holds now. For fill, the room left is capacity - water. The amount that goes in is the smaller of the requested amount and the room left. If everything fits, that is the request. If not, it is the room left, and the bottle ends up exactly full. A full bottle has no room, so the amount is 0.
drink works the same way from the other side: the amount drunk is the smaller of the request and the water inside. The water can't go below 0 because you never subtract more than it holds. Using min removes the need for separate "does it fit?" branches.
The water attribute is called water, not level. An attribute named level would hide the method level(), and bottle.level() would fail.
Step by step
- Store the capacity in
self.capacity.fillneeds it later to know how much still fits. - Set
self.waterto 0. A new bottle is empty. - In
fill, computeaddedasmin(amount, self.capacity - self.water). - Add
addedto the water with+=. Addingadded, notamount, keeps the water within the capacity. - Return
added, so the caller learns how much really went in. - In
drink, computedrunkasmin(amount, self.water). - Subtract
drunkfrom the water with-=. - Return
drunk. levelreturnsself.water.
The solution in Python
class WaterBottle:
def __init__(self, capacity: int):
self.capacity = capacity
self.water = 0
def fill(self, amount: int) -> int:
added = min(amount, self.capacity - self.water)
self.water += added
return added
def drink(self, amount: int) -> int:
drunk = min(amount, self.water)
self.water -= drunk
return drunk
def level(self) -> int:
return self.water
A run with two bottles:
b = WaterBottle(300)
print(b.fill(250), b.fill(100), b.level(), b.fill(10))
c = WaterBottle(100)
print(c.drink(10), c.fill(60), c.drink(100), c.level(), c.drink(5))
250 50 300 0
0 60 60 0 0
The second bottle shows the other limit. Drinking from the empty bottle gets 0. After fill(60), drink(100) can only take the 60 that are there, which empties it again.
Edge cases and common mistakes
- Adding
amountinstead ofadded. The bottle would overflow: with capacity 300, two fills of 250 would leave 500 in it. - Returning
amountinstead ofadded. The water stays correct, but the caller is told that everything fit. - A full or empty bottle.
fillon a full bottle anddrinkon an empty one both return 0 and change nothing. - An amount of 0. Both methods return 0, since
min(0, ...)is 0. - Naming the attribute
level. It hides the method of the same name. - Complexity. Each method does a few arithmetic operations, so each runs in O(1) time.
