Start learning free

LESSON 254 OF 573 · FUNCTIONS & CLASSES

Water Bottle Class in Kotlin: Fill and Drink

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

    KOTLIN · FUNCTIONS & CLASSES

    Water Bottle in Kotlin, explained

    Problem overview

    A 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 inside. Write a class WaterBottle that tracks the water in one bottle, in whole milliliters.

    • WaterBottle(capacity) creates an empty bottle.
    • fill(amount) pours in as much of amount as fits and returns how much actually went in.
    • drink(amount) drinks as much of amount as is there and returns how much was actually drunk.
    • level() returns the water in the bottle now.

    In a bottle of 300 that already holds 250, fill(100) pours only 50 and returns 50, and the bottle is full. A full bottle takes nothing more: fill(10) returns 0.

    Approach

    The bottle needs to remember two numbers: its capacity and its current water. Both operations follow the same pattern: the amount that really moves is 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.

    Taking the smaller of the two means the water never goes above the capacity or below 0, with no if needed. Each method is O(1).

    Step by step

    1. The water property. The header already makes capacity a property. Add var water: Int = 0: a new bottle is empty, and var because it changes. Its name differs from the method level.
    2. fill. Compute added as minOf(amount, capacity - water). If everything fits, that is amount; otherwise it is exactly the room left, and the bottle ends full. In a full bottle the room is 0, so added is 0.
    3. Update and report. Add added (not amount) to water, then return added. Returning it lets the caller see whether everything fit.
    4. drink. The same shape: drunk is minOf(amount, water), subtract it from water and return it. From an empty bottle, drunk is 0.
    5. level. Return water.

    The solution in Kotlin

    class WaterBottle(val capacity: Int) {
        var water: Int = 0
    
        fun fill(amount: Int): Int {
            val added = minOf(amount, capacity - water)
            water += added
            return added
        }
    
        fun drink(amount: Int): Int {
            val drunk = minOf(amount, water)
            water -= drunk
            return drunk
        }
    
        fun level(): Int {
            return water
        }
    }
    

    minOf is a function from Kotlin's standard library that returns the smaller of two values. val added is a local value: it is set once and never reassigned, while water is a var because it changes with every call.

    Using the class

    fun main() {
        val w = WaterBottle(300)
        println(w.fill(250))
        println(w.fill(100))
        println(w.level())
        println(w.fill(10))
        println(w.drink(100))
        println(w.level())
        println(w.drink(500))
        println(w.drink(5))
    }
    

    It prints:

    250
    50
    300
    0
    100
    200
    200
    0
    

    The sixth line shows the level after drinking 100 from a full bottle of 300. drink(500) takes only the 200 that were left, and the next drink(5) gets nothing.

    Tracing a few calls

    The table follows a bottle with capacity 100 through the calls drink(10), fill(60), drink(100) and drink(5). The bottle starts empty.

    CallWater beforeAmount that movesWater afterReturns
    drink(10)0min(10, 0) = 000
    fill(60)0min(60, 100 - 0) = 606060
    drink(100)60min(100, 60) = 60060
    drink(5)0min(5, 0) = 000

    Whenever the request is too large, the bottle ends exactly full or exactly empty. Whenever it is small enough, the water changes by exactly the requested amount.

    Why the property is not a parameter

    The capacity is written in the header with val, so it is a read-only property: a bottle never changes its size. The water is declared in the body with var, because it changes with every call. Each bottle you create has its own water, so filling one bottle leaves another untouched:

    fun main() {
        val small = WaterBottle(100)
        val big = WaterBottle(1000)
        println(small.fill(500))
        println(big.fill(500))
        println("${small.level()} ${big.level()}")
    }
    

    It prints:

    100
    500
    100 500
    

    Common mistakes

    • Adding amount instead of added. The water would go past the capacity.
    • Returning amount instead of what moved. The tests check the return values: fill(100) in the example must return 50, not 100.
    • Using water where the room is meant. The limit for fill is capacity - water, not capacity.
    • Forgetting that the bottle starts empty. A first drink must return 0.

    Edge cases to try

    • An amount of 0. fill(0) and drink(0) move nothing and return 0. The formulas handle it without a special case, since the smaller of 0 and anything non-negative is 0.
    • An exact fit. On an empty bottle of 500, fill(500) returns 500 and leaves the bottle full. Then fill(1) returns 0, because the room is 0.
    • Drinking everything. drink(level()) empties the bottle and returns what was in it. Any further drink returns 0.
    • A request far beyond the limit. The constraints allow amounts up to 10,000 with capacities as small as 1. The bottle still ends exactly full or empty, and the return value says how much really moved.

    A short program shows the exact fit, the empty bottle and the tiny capacity:

    fun main() {
        val b = WaterBottle(500)
        println(b.fill(500))
        println(b.fill(1))
        println(b.drink(b.level()))
        println(b.drink(1))
    
        val t = WaterBottle(1)
        println(t.fill(10000))
        println(t.level())
    }
    

    It prints 500, 0, 500, 0, then 1 and 1 for the tiny bottle: ten thousand milliliters offered, one accepted.

    The two return values are the part beginners most often forget. The water level alone doesn't tell the caller whether the whole request was honoured; the number returned by fill and drink does.

    Keep going with Kotlin500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free