Start learning free

LESSON 255 OF 574 · FUNCTIONS & CLASSES

Water Bottle in Swift: A Class That Respects 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 10
    Ctrl + Enter to check

    SWIFT · FUNCTIONS & CLASSES

    Water Bottle in Swift, 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 in it. The class WaterBottle tracks one bottle, with amounts in whole milliliters:

    • WaterBottle(capacity:) creates an empty bottle that holds at most capacity;
    • 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 how much water is in the bottle.

    With capacity 300, fill(250) returns 250. Then fill(100) has room for only 50, so it returns 50 and the bottle is full. A full bottle takes nothing more: fill(10) returns 0.

    Approach

    The bottle needs two numbers: its capacity, which never changes, and the water it holds now. Both limits have the same shape, so both methods use min:

    • filling adds the smaller of the amount and the room left, capacity - water;
    • drinking removes the smaller of the amount and the water present.

    The water then always stays between 0 and the capacity, and the return value tells the caller what really happened. Each method does a few operations on numbers, so each is O(1).

    Step by step

    1. Declare capacity as an Int property. fill needs it to know how much still fits.
    2. Declare water starting at 0, because a new bottle is empty. It isn't named level, since a property and a method can't share a name.
    3. In init, store the capacity: self.capacity = capacity. water already has its starting value.
    4. In fill, declare added as min(amount, capacity - water).
    5. Add added to water, not amount, so the bottle never overflows.
    6. Return added.
    7. In drink, declare drunk as min(amount, water), subtract it from water, and return it.
    8. level() returns water.

    The solution in Swift

    class WaterBottle {
        var capacity: Int
        var water: Int = 0
    
        init(capacity: Int) {
            self.capacity = capacity
        }
    
        func fill(_ amount: Int) -> Int {
            let added = min(amount, capacity - water)
            water += added
            return added
        }
    
        func drink(_ amount: Int) -> Int {
            let drunk = min(amount, water)
            water -= drunk
            return drunk
        }
    
        func level() -> Int {
            return water
        }
    }
    

    A run on a bottle of capacity 300:

    let w = WaterBottle(capacity: 300)
    print(w.fill(250))
    print(w.fill(100))
    print(w.level())
    print(w.fill(10))
    print(w.drink(120))
    print(w.level())
    print(w.drink(500))
    print(w.level())
    
    250
    50
    300
    0
    120
    180
    180
    0
    

    The last drink(500) returns 180, not 500: only 180 was in the bottle, and the bottle is now empty.

    Swift details worth knowing

    • min is a global function that returns the smaller of two values of the same type. Both arguments here are Int.
    • let inside a method makes added and drunk constants: they are computed once and never change.
    • Only capacity is set in init. A property with a default value (= 0) doesn't need an assignment there.

    Why not an if?

    You could also write fill with an if: if the amount is more than the room left, pour only the room left, otherwise pour all of it. That is exactly what min does in one line. Using min removes a branch you could get wrong, and it makes the two methods mirror each other: the same shape, with a different limit.

    What goes wrong without the limit

    A version of fill that simply does water += amount and returns amount compiles and looks fine for small amounts. With capacity 300, calling fill(250), then fill(100), then level() prints 250, 100 and 350: a bottle of 300 holding 350. The lesson's tests catch this because they check both the returned amounts and the level.

    Edge cases and common mistakes

    • Adding amount instead of added. The bottle would go above its capacity.
    • Returning amount. The methods must return what really happened: fill(100) into a bottle with room for 50 returns 50.
    • A full bottle. The room left is 0, so fill returns 0.
    • An empty bottle. drink returns 0 and the level stays at 0. A drink of 0 changes nothing either.
    • Two limits mixed up. fill is limited by the room left (capacity - water), drink by the water present, not by the capacity.

    Negative and oversized amounts

    The lesson only gives amounts from 0 up to 10,000, so a negative amount never reaches the methods. If it did, min would pick that negative number and fill would drain the bottle. Real code would check for it. Here the constraints let the two min lines stay as simple as they are.

    An amount larger than the capacity is fine: fill(10000) on an empty bottle of 300 adds 300 and returns 300.

    Complexity

    Every method does a constant number of arithmetic operations on two integers, so each one runs in O(1) time. The class stores two integers, so it uses O(1) space, no matter how many times you fill and drink.

    Testing

    A bottle of capacity 100, in a file of your own, is a good small case:

    • drink(10) on the empty bottle returns 0.
    • fill(60) returns 60.
    • drink(100) returns 60, because only 60 were there.
    • level() returns 0, and a further drink(5) returns 0.

    Run in that order, the five calls print 0, 60, 60, 0 and 0.

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