Problem overview
A water bottle holds at most a fixed amount, called its capacity. 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.fill(amount)pours in as much ofamountas fits and returns how much actually went in.drink(amount)drinks as much ofamountas is there and returns how much was actually drunk.level()returns how much water is in the bottle now.
For a bottle of capacity 300, fill(250) returns 250. Then fill(100) returns only 50, because that is all the room left, and the bottle is full. A full bottle takes nothing, so fill(10) returns 0.
Approach
The bottle needs two numbers: the capacity, which never changes, and the water it holds now. Both methods follow the same pattern. The amount that really moves is the smaller of two numbers: what was asked for and what is possible. For fill, what is possible is the room left, capacity - water. For drink, it is the water in the bottle. Moving that amount, never the requested one, keeps water between 0 and capacity without any extra checks. Each method does a constant amount of work.
Step by step
- Declare
capacity, anintfield. It never changes, butfillneeds it to know how much still fits. - Declare
water, anintthat starts at 0. A field can start with a value, so every new bottle starts empty. - In the constructor, write
this->capacity = capacity;. The parameter has the same name as the field, sothis->picks the field.wateralready starts at 0 and needs nothing here. - In
fill, computeaddedasmin(amount, capacity - water). If everything fits, that isamount. If not, it is the room left, and the bottle ends up exactly full. A full bottle has no room, soaddedis 0. - Add
addedtowaterwith+=. Addingadded, notamount, is what keeps the water within the capacity. - Return
added, so the caller learns how much really went in. - In
drink, computedrunkasmin(amount, water). Nobody can drink more than the bottle holds, so from an empty bottledrunkis 0. - Subtract
drunkfromwaterand returndrunk. The water never goes below 0 becausedrunkis at mostwater. level()returnswater.
The solution in C++
class WaterBottle {
public:
int capacity;
int water = 0;
WaterBottle(int capacity) {
this->capacity = capacity;
}
int fill(int amount) {
int added = min(amount, capacity - water);
water += added;
return added;
}
int drink(int amount) {
int drunk = min(amount, water);
water -= drunk;
return drunk;
}
int level() {
return water;
}
};
The field is called water rather than level because a class can't have a data member and a member function with the same name.
A run
Here is a bottle of capacity 300 being filled and emptied:
#include <iostream>
#include <algorithm>
using namespace std;
// the class above goes here
int main() {
WaterBottle w(300);
cout << w.fill(250) << " " << w.fill(100) << " " << w.level() << " " << w.fill(10) << endl;
cout << w.drink(100) << " " << w.drink(500) << " " << w.level() << endl;
}
It prints:
250 50 300 0
100 200 0
The first line shows the two fills and the level: 250 went in, then only 50 of the next 100, the level is 300, and the full bottle takes none of the last 10. On the second line drink(100) drinks 100, drink(500) gets only the 200 that remain, and the level is 0.
Details worth knowing
mincomes from<algorithm>. Both arguments must have the same type, here twoints. In a program of your own, include<algorithm>and writestd::min, or addusing namespace std;as the examples here do.- The return value is information. Because
fillanddrinkreturn what really happened, the caller can tell whether everything fit without callinglevel()before and after. - An amount of 0 is allowed. It moves nothing and returns 0.
An empty bottle
Drinking from an empty bottle is the other limit. This bottle has capacity 100 and starts empty:
#include <iostream>
#include <algorithm>
using namespace std;
// the class above goes here
int main() {
WaterBottle w(100);
cout << w.drink(10) << " " << w.fill(60) << " " << w.drink(100) << " " << w.level() << " " << w.drink(5) << endl;
}
It prints:
0 60 60 0 0
The first drink(10) gets nothing because there is nothing to drink. After fill(60), drink(100) returns 60, the level is back to 0, and the last drink(5) returns 0 again. No call ever pushes the level below 0 or above the capacity, whatever amounts it is given.
Common mistakes
- Adding
amounttowaterand then trimming afterwards. It works if you are careful, but you still have to work out how much was really added to return it, so computingaddedfirst is simpler. - Using
capacity - amountinstead ofcapacity - wateras the room left. The room depends on what is already inside. - Giving the field the name
level, which clashes with the method. - Returning
amountinstead ofadded. The caller would then believe the whole amount went in even when the bottle was nearly full, and the return value would tell nothing the caller didn't already know. - Forgetting that
fillanddrinkmust return a value. A non-void function that reaches its end withoutreturnhas undefined behaviour in C++.
