Start learning free

LESSON 250 OF 569 · FUNCTIONS & CLASSES

Water Bottle in Java: Limits Inside a Class

Practice O(1) Read the full explanation

A water 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. Write a class WaterBottle that keeps track of how much water is in one bottle as you fill it and drink from it. Amounts are whole milliliters.

Implement the class WaterBottle:

  • WaterBottle(capacity) creates an empty bottle that holds at most capacity.
  • fill(amount) pours in amount, but only as much as fits: the water never goes above the capacity. It returns how much actually went in.
  • drink(amount) drinks amount, but only as much as is in the bottle: the water never goes below 0. It returns how much was actually drunk.
  • level() returns how much water is in the bottle now.

The input is a bottle's capacity and a list of operations, applied in order to one new WaterBottle. The output lists what the fill, drink and level calls return, in order. The starter code spells these names the way your language does (in Go and Rust, WaterBottle is a struct with methods). Give the fields names that differ from the methods' names: in several languages a field named level would clash with the method level().

 

Example 1:

Input:
    WaterBottle(500)
    fill(200)
    drink(50)
    level()
Output: 200 50 150
Explanation: The empty bottle has room for 500, so all 200 go in. Drinking 50 of them leaves 150.

Example 2:

Input:
    WaterBottle(300)
    fill(250)
    fill(100)
    level()
    fill(10)
Output: 250 50 300 0
Explanation: After the first fill, only 50 more fit: fill(100) pours in 50 and returns 50, and the bottle
is full. A full bottle takes nothing more, so fill(10) returns 0.

Example 3:

Input:
    WaterBottle(100)
    drink(10)
    fill(60)
    drink(100)
    level()
    drink(5)
Output: 0 60 60 0 0
Explanation: The bottle starts empty, so the first drink gets nothing. drink(100) can only drink
the 60 that are there, which empties the bottle again.

 

Constraints:

  • 1 <= capacity <= 104
  • 0 <= amount <= 104
  • At most 250 operations on one WaterBottle.

Note:

Each method should run in O(1) time: the bottle only needs to remember its capacity and how much water it holds.

STEP 1 OF 10
    Ctrl + Enter to check

    JAVA · FUNCTIONS & CLASSES

    Water Bottle in Java, explained

    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

    1. Declare capacity and water. water starts at 0 because every new bottle is empty.
    2. The constructor stores capacity with this.capacity = capacity. water already has its starting value.
    3. In fill, compute added as Math.min(amount, capacity - water). If everything fits it is amount; otherwise it is the room left. A full bottle has no room, so added is 0.
    4. Add added, not amount, to water. That is what keeps the level within the capacity.
    5. Return added, so the caller learns whether everything fit.
    6. In drink, compute drunk as Math.min(amount, water), subtract it from water and return it. The water can't go below 0 because drunk is at most water.
    7. level returns water.

    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:

    CallPossible amountReturnswater afterwards
    new bottle0
    drink(10)0 in the bottle00
    fill(60)100 of room6060
    drink(100)60 in the bottle600
    drink(5)0 in the bottle00

    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 amount and then trimming. It is easy to return the wrong number that way. Computing added first gives both the new level and the return value.
    • Mixing up the two limits. fill is limited by capacity - water, drink by water.
    • Returning the requested amount instead of the actual one. fill(100) into a bottle with room for 50 must return 50.
    • Setting water to capacity in the constructor. A new bottle is empty.
    Keep going with Java500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free