Problem overview
A WaterBottle holds at most capacity milliliters. Fill(amount) pours water in, but only as much as fits, and returns how much actually went in. Drink(amount) drinks, but only as much as is in the bottle, and returns how much was actually drunk. Level() returns the water in the bottle now. A new bottle is empty.
For a capacity of 300: Fill(250) returns 250. Then Fill(100) returns only 50, because only 50 more fit, and the bottle is full. A further Fill(10) returns 0.
Approach
The class remembers two numbers: the capacity, which never changes, and the water, which does. The rule for both methods is the same: take the smaller of what was asked and what is possible.
- For
Fill, what is possible is the room left,capacity - water. - For
Drink, what is possible is the water in the bottle.
Computing the amount that really moved, then applying it and returning it, keeps the water between 0 and the capacity without any extra checks. Each method is O(1).
Step by step
- Fields: a
public int capacity, andpublic int water = 0, since every bottle starts empty. - Constructor: store the parameter in
this.capacity. The parameter has the field's name, sothis.picks the field.wateralready starts at 0. - In
Fill, computeaddedasMath.Min(amount, capacity - water). If everything fits, that isamount; otherwise it is the room left and the bottle ends exactly full. A full bottle has no room, soaddedis 0. - Add
added, notamount, towater, then returnadded, so the caller learns what really happened. - In
Drink, computedrunkasMath.Min(amount, water), subtract it fromwaterand return it. From an empty bottle,drunkis 0. Levelreturnswater.
The solution in C#
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;
}
}
Using it:
var b = new WaterBottle(300);
Console.WriteLine(b.Fill(250));
Console.WriteLine(b.Fill(100));
Console.WriteLine(b.Level());
Console.WriteLine(b.Drink(500));
Console.WriteLine(b.Level());
This prints:
250
50
300
300
0
Math.Min comes from the System namespace, so a program of your own needs using System; at the top.
Edge cases and common mistakes
- Overfilling. Writing
water += amountlets the level pass the capacity. Add only what fits. - Returning the request.
Fillreturns what went in, not what was offered:Fill(100)into a bottle with room for 50 returns 50. - Drinking from an empty bottle. The result is 0 and the level stays at 0, never negative.
- Amount 0. Both methods return 0 and change nothing.
- Draining more than is there.
Drink(100)on a bottle holding 60 returns 60 and leaves it empty.
Why the class guards its own limits
The bottle's rule is that the water never goes above the capacity or below 0. Putting that rule inside Fill and Drink means every caller gets it for free: nobody has to check the level before pouring. The return values also carry information. A caller who pours 100 and gets 50 back knows the bottle is now full and 50 did not fit.
Think of the arithmetic as a small table for a bottle with capacity 300 that holds 250:
| Call | Asked | Possible | Moved |
|---|---|---|---|
Fill(100) | 100 | 300 - 250 = 50 | 50 |
Drink(500) | 500 | 300 | 300 |
Drink(5) on the empty bottle | 5 | 0 | 0 |
Testing
Try these sequences on a new bottle:
- Capacity 500:
Fill(200)returns 200,Drink(50)returns 50,Level()returns 150. - Capacity 100:
Drink(10)returns 0 because the bottle is empty.Fill(60)returns 60.Drink(100)returns 60.Level()returns 0, andDrink(5)returns 0.
Separate bottles
Every bottle has its own capacity and water. If you create new WaterBottle(100) and new WaterBottle(10) and call Fill(60) on each, the first holds 60 and the second holds 10, because the second one stops when it is full. The same method gives different results because each object brings its own fields.
The capacity could be made unchangeable with the readonly keyword, but the lesson keeps it a plain field: no method ever changes it, which is what matters here.
Mistakes worth recognising
- Checking after changing. Subtracting first and then testing for a negative level is harder to get right than clamping the amount first, which never produces an invalid level.
- Mixing up the two limits. The limit for
Fillis the free space,capacity - water, not the capacity itself. UsingMath.Min(amount, capacity)lets a half-full bottle overflow. - Returning the level instead of the moved amount.
FillandDrinkreturn how much moved.Levelis the method that reports what is in the bottle. - Reusing one name. The field is
water, notlevel, because a field and a method in the same class can't share a name.
Key takeaways
- Take the smaller of the request and what is possible, apply it, and return it.
- Putting the limits in the methods means every caller gets them.
- Each bottle has its own fields, so two bottles never interfere.
The statement allows 0 for amount, and nothing special is needed for it: the smaller of 0 and anything non-negative is 0, so the level stays the same.
