Problem overview
A bottle holds at most capacity milliliters. WaterBottle tracks how much water is inside as you fill it and drink from it:
new WaterBottle(capacity)makes 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.
With a capacity of 300, fill(250) returns 250. Then fill(100) returns only 50, because only 50 fit, and the bottle is full. A further fill(10) returns 0.
Approach
The bottle needs two numbers: the capacity, which never changes, and the water, which starts at 0. Both operations have the same shape: the amount that really moves is the smaller of what was asked for and what is possible. For fill, the possible amount is the room left, capacity - water. For drink, it is the water in the bottle. Computing that amount first, then applying it, keeps the water between 0 and the capacity without any special cases, and the same number is what the method returns.
You could also write the limits with if statements: if the amount is more than the room left, pour only the room left, otherwise pour all of it. That is correct, but it needs a branch and a second copy of the logic in each method. Math.min says the same thing in one line.
Step by step
- Declare
capacityandwater.waterstarts at 0 because every new bottle is empty. - The constructor stores
capacitywiththis.capacity = capacity.wateralready has its starting value. - In
fill, computeaddedasMath.min(amount, capacity - water). If everything fits it isamount; otherwise it is the room left. A full bottle has no room, soaddedis 0. - Add
added, notamount, towater. That is what keeps the level within the capacity. - Return
added, so the caller learns whether everything fit. - In
drink, computedrunkasMath.min(amount, water), subtract it fromwaterand return it. The water can't go below 0 becausedrunkis at mostwater. levelreturnswater.
The solution in Java
class WaterBottle {
public int capacity;
public int water = 0;
public WaterBottle(int capacity) {
this.capacity = capacity;
}
public int fill(int amount) {
int added = Math.min(amount, capacity - water);
water += added;
return added;
}
public int drink(int amount) {
int drunk = Math.min(amount, water);
water -= drunk;
return drunk;
}
public int level() {
return water;
}
}
Math.min is a static method of the Math class, so you call it on the class name, with no object. The fields have names that differ from the methods, so there is no clash between water and level().
Using the class
WaterBottle b = new WaterBottle(300);
System.out.println(b.fill(250));
System.out.println(b.fill(100));
System.out.println(b.level());
System.out.println(b.fill(10));
System.out.println(b.drink(100));
System.out.println(b.drink(300));
System.out.println(b.level());
prints:
250
50
300
0
100
200
0
The last drink(300) returns 200: only 200 were left, so that is all that could be drunk.
Tracing the water level
For a bottle with a capacity of 100, the calls drink(10), fill(60), drink(100) and drink(5) work like this:
| Call | Possible amount | Returns | water afterwards |
|---|---|---|---|
| new bottle | 0 | ||
drink(10) | 0 in the bottle | 0 | 0 |
fill(60) | 100 of room | 60 | 60 |
drink(100) | 60 in the bottle | 60 | 0 |
drink(5) | 0 in the bottle | 0 | 0 |
In every row the return value is the smaller of the requested amount and the possible amount, and that same value is what moves in or out of the bottle.
Special cases
- Drinking from an empty bottle returns 0 and changes nothing.
- Filling a full bottle returns 0, because the room left is
capacity - water, which is 0. - An amount of 0 moves nothing: both methods return 0.
- An amount that exactly fits (filling the room left, or drinking everything) returns the full amount and leaves the bottle full or empty.
More runs
WaterBottle b = new WaterBottle(500);
System.out.println(b.fill(200));
System.out.println(b.drink(50));
System.out.println(b.level());
WaterBottle c = new WaterBottle(100);
System.out.println(c.fill(0));
System.out.println(c.fill(100));
System.out.println(c.fill(1));
System.out.println(c.drink(100));
System.out.println(c.level());
prints:
200
50
150
0
100
0
100
0
The first three lines are the roomy bottle: 200 go in, 50 are drunk, 150 are left. In the second bottle, filling 0 moves nothing, filling exactly the capacity returns 100, the next fill(1) returns 0 because the bottle is full, and drinking 100 empties it, so level() is 0.
Complexity
Each method is a few arithmetic operations, so it runs in O(1) time, and the bottle stores two integers.
What happens without the limit
A version of fill that ignores the room left looks natural at first:
public int fill(int amount) {
water += amount;
return amount;
}
On a bottle with a capacity of 300, after fill(250) the call fill(100) returns 100 and level() then reports 350. The bottle holds more than its capacity, so the object is in a state that should be impossible. The Math.min line is what enforces the rule, and it does so in the one place where water enters the bottle. The same reasoning applies to drink: without the limit, the level would go below 0.
Common mistakes
- Adding
amountand then trimming. It is easy to return the wrong number that way. Computingaddedfirst gives both the new level and the return value. - Mixing up the two limits.
fillis limited bycapacity - water,drinkbywater. - Returning the requested amount instead of the actual one.
fill(100)into a bottle with room for 50 must return 50. - Setting
watertocapacityin the constructor. A new bottle is empty.
