Start learning free

LESSON 555 OF 568 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Thermostat in C++: Keeping a Private Field Within Limits

Practice O(1) Read the full explanation

A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could change the target directly, nothing would stop it from setting 95. So the target is a private field: only the thermostat's own methods can change it, and every one of them keeps it between 10 and 30.

Implement the class Thermostat:

  • Thermostat() creates a thermostat whose target is 20.
  • setTarget(t) sets the target to t. A value below 10 sets it to 10, and a value above 30 sets it to 30.
  • warmer() raises the target by 1 and cooler() lowers it by 1, but neither ever takes it outside 10 to 30: at 30, warmer() leaves it at 30, and at 10, cooler() leaves it at 10.
  • getTarget() returns the target. It is the only way for other code to read it.

Keep the target private the way the Access Modifiers lesson does in your language: self._target in Python (private by convention), a #target field in JavaScript, and the private keyword in Java, C++, C#, TypeScript, Kotlin, Scala and Swift.

The input is a list of operations, applied in order to one new Thermostat. The output lists what the getTarget calls return, in order. The starter code spells these names the way your language does: set_target and get_target in Python, SetTarget, Warmer, Cooler and GetTarget in C#.

 

Example 1:

Input:
    getTarget()
    warmer()
    warmer()
    getTarget()
    cooler()
    getTarget()
Output: 20 22 21
Explanation: A new thermostat starts at 20. Two warmer() calls take it to 22, and cooler() brings it back to 21.

Example 2:

Input:
    setTarget(35)
    getTarget()
    warmer()
    getTarget()
    cooler()
    getTarget()
Output: 30 30 29
Explanation: 35 is above 30, so the target becomes 30. warmer() can't go past 30, so the target stays 30.
cooler() lowers it to 29.

Example 3:

Input:
    setTarget(4)
    getTarget()
    cooler()
    getTarget()
    setTarget(10)
    warmer()
    getTarget()
Output: 10 10 11
Explanation: 4 is below 10, so the target becomes 10, and cooler() can't go below 10. setTarget(10) keeps 10,
since 10 itself is allowed, and warmer() raises it to 11.

 

Constraints:

  • -1000 <= t <= 1000
  • A test case has from 1 to 1000 operations, and at least one of them is getTarget.
STEP 1 OF 8
    Ctrl + Enter to check

    C++ · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Thermostat in C++, explained

    Problem overview

    A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could change the target directly, nothing would stop it from setting 95. So the target is a private field: only the thermostat's own methods can change it, and every one of them keeps it between 10 and 30.

    The class Thermostat has these methods:

    • Thermostat() creates a thermostat whose target is 20.
    • setTarget(t) sets the target to t. A value below 10 sets it to 10, and a value above 30 sets it to 30. Exactly 10 and exactly 30 are allowed.
    • warmer() raises the target by 1 and cooler() lowers it by 1, but neither ever takes it outside 10 to 30.
    • getTarget() returns the target. It is the only way for other code to read it.

    A new thermostat starts at 20. Two warmer() calls take it to 22, and cooler() brings it back to 21. After setTarget(35) the target is 30, and another warmer() leaves it at 30.

    Approach

    The 10-to-30 rule has to hold after every change. The simplest way to guarantee that is to write the rule once, in setTarget, and let every other method go through it. warmer() then just asks setTarget for target + 1, and cooler() asks for target - 1. At 30, the request for 31 is turned back into 30, and at 10, the request for 9 becomes 10 again. There is no second copy of the limits to forget about, and each method runs in constant time.

    The field is private, so code outside the class can't bypass the rule by writing to it. That is the point of the exercise: the class protects its own data.

    Step by step

    1. Declare the field int target; in the private: section. What is under private: is hidden from code outside the class; what is under public: is what other code can call.
    2. Set it to 20 in the constructor. Every new thermostat starts at 20.
    3. In setTarget, handle a value that is too low. If t < 10, set target to 10. The value isn't refused; the target becomes the lowest one allowed.
    4. Handle a value that is too high with an else if: if t > 30, set target to 30.
    5. Keep every other value with an else: target = t. The values 10 and 30 end up here and are kept as they are.
    6. warmer() calls setTarget(target + 1).
    7. cooler() calls setTarget(target - 1).
    8. getTarget() returns target. It reports the value and can't change it.

    The solution in C++

    class Thermostat {
    private:
        int target;
    public:
        Thermostat() {
            target = 20;
        }
    
        void setTarget(int t) {
            if (t < 10) {
                target = 10;
            } else if (t > 30) {
                target = 30;
            } else {
                target = t;
            }
        }
    
        void warmer() {
            setTarget(target + 1);
        }
    
        void cooler() {
            setTarget(target - 1);
        }
    
        int getTarget() {
            return target;
        }
    };

    A run

    This follows the second and third examples, using the methods only:

    #include <iostream>
    using namespace std;
    
    // the class above goes here
    int main() {
        Thermostat a;
        cout << a.getTarget() << endl;
        a.setTarget(35);
        cout << a.getTarget() << endl;
        a.warmer();
        cout << a.getTarget() << endl;
        a.cooler();
        cout << a.getTarget() << endl;
    
        Thermostat b;
        b.setTarget(4);
        cout << b.getTarget() << endl;
        b.cooler();
        cout << b.getTarget() << endl;
        b.setTarget(10);
        b.warmer();
        cout << b.getTarget() << endl;
    }

    It prints:

    20
    30
    30
    29
    10
    10
    11

    The first group shows the upper limit: 35 becomes 30, warmer() stays at 30 and cooler() gives 29. The second group shows the lower limit: 4 becomes 10, cooler() stays at 10, and after setTarget(10) a warmer() gives 11.

    What private does

    Code outside the class can call the public methods but can't touch the field. Writing t.target in a program of your own is a compile error:

    error: 'int Thermostat::target' is private within this context

    That is exactly what keeps the rule safe: the only way to change the target is through methods that enforce the limits. The code inside the class's own methods can use target freely.

    Another way

    C++17 has a function that does what the three branches do: clamp, from <algorithm>. clamp(t, 10, 30) returns 10 if t is below 10, 30 if it is above 30, and t otherwise.

    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    class ClampedThermostat {
    private:
        int target;
    public:
        ClampedThermostat() {
            target = 20;
        }
        void setTarget(int t) {
            target = clamp(t, 10, 30);
        }
        void warmer() {
            setTarget(target + 1);
        }
        int getTarget() {
            return target;
        }
    };
    
    int main() {
        ClampedThermostat c;
        c.setTarget(95);
        cout << c.getTarget() << endl;
        c.setTarget(-5);
        cout << c.getTarget() << endl;
        c.setTarget(17);
        c.warmer();
        cout << c.getTarget() << endl;
    }

    It prints:

    30
    10
    18

    The if version above is the one to write first, because it shows the rule step by step. Both give the same results for every integer.

    Testing

    The values at the edges are the ones worth trying. Start from a new thermostat and call setTarget(10), then cooler(): the target must stay 10. Call setTarget(30), then warmer(): it must stay 30. Call setTarget(-1000) and setTarget(1000), the extremes the lesson allows, and check that you get 10 and 30. Finally, call warmer() twenty-five times from a new thermostat: the target climbs from 20 to 30 and stays there, instead of passing 30.

    Common mistakes

    • Writing the limits again in warmer and cooler, then changing one place and forgetting the others. Calling setTarget keeps the rule in one place.
    • Checking the limits with <= and >=, for example t <= 10. The result is still right, because 10 maps to 10, but the condition no longer says what the rule says: only values below 10 are changed. Matching the rule in the condition makes the code easier to read and to fix.
    • Writing target++ in warmer(): nothing stops it at 30.
    • Putting target under public:. The tests would still pass, but the class would no longer protect the rule.
    Keep going with C++500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free