Start learning free

LESSON 560 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Thermostat in Scala: Private Fields

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

    SCALA · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Thermostat in Scala, 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 each of them keeps it in range.

    • Thermostat() starts with a target of 20.
    • setTarget(t) sets the target to t, but a value below 10 gives 10 and a value above 30 gives 30.
    • warmer() raises the target by 1 and cooler() lowers it by 1, never leaving the range 10 to 30.
    • getTarget() returns the target. It is the only way for other code to read it.

    A new thermostat reports 20. Two calls to warmer() give 22, and cooler() brings it to 21.

    Approach: one place that enforces the rule

    The range rule belongs in one method, setTarget. If warmer and cooler simply call setTarget with target + 1 or target - 1, they get the rule for free: at 30, warmer() asks for 31 and setTarget turns it back into 30.

    Step by step

    1. Declare the field at the top of the class: private var target: Int = 20. It is a var because the methods change it, and private so that only they can.
    2. In setTarget, handle a low value. If t < 10, set target to 10. A value that is too low isn't refused; it becomes the lowest target allowed.
    3. Handle a high value with else if (t > 30), setting target to 30.
    4. Every other value goes in the else branch and is stored as it is. Because 10 and 30 themselves are allowed, they end up here.
    5. warmer calls setTarget(target + 1).
    6. cooler calls setTarget(target - 1).
    7. getTarget returns target.

    The solution in Scala

    class Thermostat {
        private var target: Int = 20
    
        def setTarget(t: Int): Unit = {
            if (t < 10) {
                target = 10
            } else if (t > 30) {
                target = 30
            } else {
                target = t
            }
        }
    
        def warmer(): Unit = {
            setTarget(target + 1)
        }
    
        def cooler(): Unit = {
            setTarget(target - 1)
        }
    
        def getTarget(): Int = {
            return target
        }
    }
    

    Scala details:

    • private is checked by the compiler. Code outside the class that writes t.target = 95 is rejected: "variable target cannot be accessed as a member of (t : Thermostat) ... private variable target can only be accessed from class Thermostat."
    • Methods can call each other by their plain names. warmer calls setTarget(...) on the same object.
    • if / else if / else works as a chain: the first condition that is true decides, and the rest are skipped.

    Using the class

    val th = new Thermostat
    println(th.getTarget())
    th.warmer(); th.warmer()
    println(th.getTarget())
    th.cooler()
    println(th.getTarget())
    th.setTarget(35)
    println(th.getTarget())
    th.warmer()
    println(th.getTarget())
    th.cooler()
    println(th.getTarget())
    th.setTarget(4)
    println(th.getTarget())
    th.cooler()
    println(th.getTarget())
    th.setTarget(10)
    th.warmer()
    println(th.getTarget())

    It prints:

    20
    22
    21
    30
    30
    29
    10
    10
    11

    35 becomes 30, and warmer() leaves it at 30. After cooler() it is 29. 4 becomes 10, cooler() leaves it at 10, and after setTarget(10) a warmer() gives 11.

    Why a private field

    With a public var, any line of code in the program could write t.target = 95, and the class could not stop it. Making the field private turns the rule into a promise the compiler helps keep: the only code that can touch target is inside the class, and all of it goes through the same check. This is called encapsulation. The class decides what is allowed, and the rest of the program uses the public methods.

    It also explains getTarget. Other code still needs to know the temperature, but reading it through a method gives nobody a way to change it. If the rule changes later, say the range becomes 5 to 35, only the numbers inside setTarget change, and everything that uses the thermostat keeps working.

    What happens at the edges

    The calls at the limits are the ones worth tracing. When the target is 30, warmer() calls setTarget(31). The first test, 31 < 10, is false, the second, 31 > 30, is true, so the target is set to 30 and does not change. Likewise at 10, cooler() calls setTarget(9), which lands in the first branch and sets 10.

    The order of the branches does not matter for correctness here, because no number is both below 10 and above 30. What matters is that every number falls into exactly one branch, so the target is always set to something, and that something is always in range.

    The program above printed this behaviour: after setTarget(35) the target was 30, a warmer() kept it at 30, and a cooler() took it to 29.

    Complexity

    Every method does a few comparisons and at most one assignment, so each runs in O(1) time with one stored integer.

    Testing

    Use a new thermostat for each sequence:

    • getTarget(), warmer(), warmer(), getTarget(), cooler(), getTarget() give 20, 22 and 21.
    • setTarget(35), getTarget(), warmer(), getTarget(), cooler(), getTarget() give 30, 30 and 29.
    • setTarget(4), getTarget(), cooler(), getTarget(), setTarget(10), warmer(), getTarget() give 10, 10 and 11.

    The second one checks the top limit, and the third checks the bottom limit and that 10 itself is accepted. Values such as -1000 and 1000 follow the same two branches and end at 10 and 30.

    Common mistakes

    • Changing the field directly in warmer. target += 1 skips the range check, and the target can reach 31.
    • Using <= or >=. 10 and 30 are valid targets and must be kept as they are.
    • Refusing out-of-range values instead of moving them to the nearest limit.
    • Leaving the field public. Without private, other code could set any value.
    Keep going with Scala500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free