Problem overview
A water bottle holds at most a fixed amount, in whole milliliters. You can't pour in more than fits, and you can't drink more than is in it. WaterBottle tracks one bottle:
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 with capacity 300, fill(250) returns 250. Then fill(100) returns only 50, because that is all the room left, and the bottle is full. Another fill(10) returns 0.
Approach: clamp each amount to what is possible
The bottle needs to remember two things: its capacity, which never changes, and the water in it, which does. Both operations work the same way: the amount that really moves is the smaller of what was asked for and what is possible.
- For
fill, what is possible is the room left,capacity - water. - For
drink, what is possible is the water that is there,water.
Taking the smaller of the two keeps the water between 0 and the capacity without any if. Returning the amount that moved tells the caller what really happened.
Step by step
- Declare the field
wateras avarof typeIntstarting at 0. The header already makescapacitya read-only field, because of itsval. - In
fill, computeaddedasmath.min(amount, capacity - water). If everything fits it isamount; otherwise it is the room left. For a full bottle the room is 0, soaddedis 0. - Add
addedtowaterwith+=. Addingaddedrather thanamountkeeps the water within the capacity. - Return
added. - In
drink, computedrunkasmath.min(amount, water). - Subtract
drunkfromwaterwith-=. The water can't go below 0 becausedrunkis at mostwater. - Return
drunk. levelreturnswater.
The solution in Scala
class WaterBottle(val capacity: Int) {
var water: Int = 0
def fill(amount: Int): Int = {
val added = math.min(amount, capacity - water)
water += added
return added
}
def drink(amount: Int): Int = {
val drunk = math.min(amount, water)
water -= drunk
return drunk
}
def level(): Int = {
return water
}
}
Scala details:
math.minreturns the smaller of two numbers. It is available without an import.valinside a method creates a local value that can't be reassigned.addedanddrunkare computed once and then only read.- The field is not named
level. A field calledlevelwould clash with the methodlevel(), which is why the field iswater.
Using the class
val w = new WaterBottle(300)
println(w.fill(250))
println(w.fill(100))
println(w.level())
println(w.fill(10))
println(w.drink(100))
println(w.drink(5))
println(w.level())
It prints:
250
50
300
0
100
5
195
The second fill returns 50 and leaves the bottle at 300; the third returns 0. Drinking 100 from a full bottle returns 100, drinking 5 more returns 5, and 195 is left.
A bottle of capacity 100 that starts empty shows the other limit. Here drink(10) returns 0, fill(60) returns 60, drink(100) returns 60 because that is all there is, and level() then returns 0.
Why min works for both methods
Think of the two limits as a range: the water is always between 0 and capacity. Pouring moves the level up and is stopped by the top of the range, and drinking moves it down and is stopped by the bottom. In both cases the distance to the limit is the most that can move, so math.min(amount, distance) is the whole rule. The distance to the top is capacity - water, and the distance to the bottom is water itself.
Because the methods return the amount that moved, the caller can tell whether the request was fully met. If fill(100) returns 100, everything fit. If it returns less, the bottle is now full. Passing 0 as the amount is fine too: fill(0) and drink(0) move nothing and return 0.
Tracing a run
Take the bottle with capacity 300 from the example and follow the water field through the first four calls:
fill(250): the room is 300 - 0 = 300, the smaller of 250 and 300 is 250, so the water becomes 250.fill(100): the room is 300 - 250 = 50, the smaller of 100 and 50 is 50, so the water becomes 300 and the call returns 50.level()returns 300.fill(10): the room is 0, so nothing is added and the call returns 0.
The program above printed exactly these returns: 250, 50, 300 and 0. The same line of code, math.min(amount, capacity - water), handled all three kinds of fill: one that fits, one that partly fits and one with no room.
Complexity
Each method does a fixed number of comparisons and additions, so each one runs in O(1) time. The object stores two integers.
Testing
Try these on a new bottle each time, and compare with the results:
- Capacity 500:
fill(200)returns 200,drink(50)returns 50, andlevel()is 150. - Capacity 300:
fill(250),fill(100),level(),fill(10)return 250, 50, 300 and 0. - Capacity 100:
drink(10),fill(60),drink(100),level(),drink(5)return 0, 60, 60, 0 and 0.
The third case covers both limits: the bottle is empty at the start, and an oversized drink empties it again.
Common mistakes
- Adding
amountinstead ofadded. The water would go above the capacity. - Returning
amountinstead of what actually moved.fill(100)on a bottle with 50 of room must return 50. - Letting the level go negative. Drinking from an empty bottle returns 0 and changes nothing.
- Forgetting that the bottle starts empty. The first
fillhas the whole capacity as room.
