Start learning free

LESSON 254 OF 573 · FUNCTIONS & CLASSES

Water Bottle in Scala: A Class with Limits

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 8
    Ctrl + Enter to check

    SCALA · FUNCTIONS & CLASSES

    Water Bottle in Scala, explained

    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

    1. Declare the field water as a var of type Int starting at 0. The header already makes capacity a read-only field, because of its val.
    2. In fill, compute added as math.min(amount, capacity - water). If everything fits it is amount; otherwise it is the room left. For a full bottle the room is 0, so added is 0.
    3. Add added to water with +=. Adding added rather than amount keeps the water within the capacity.
    4. Return added.
    5. In drink, compute drunk as math.min(amount, water).
    6. Subtract drunk from water with -=. The water can't go below 0 because drunk is at most water.
    7. Return drunk.
    8. level returns water.

    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.min returns the smaller of two numbers. It is available without an import.
    • val inside a method creates a local value that can't be reassigned. added and drunk are computed once and then only read.
    • The field is not named level. A field called level would clash with the method level(), which is why the field is water.

    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, and level() 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 amount instead of added. The water would go above the capacity.
    • Returning amount instead 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 fill has the whole capacity as room.
    Keep going with Scala500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free