Problem overview
The method clampAll gets an Array[Int] called nums and two limits, low and high, and returns an ArrayBuffer[Int] in which every number is moved inside the limits, in the same order. A number below low becomes low, a number above high becomes high, and any other number stays as it is. Built-in min, max and clamp functions are not allowed, so the comparing is done by hand.
For nums = [12, -3, 7, 25] with limits 0 and 20, the answer is [12, 0, 7, 20]. With [-8, -1, 4] and limits -5 and 5 it is [-5, -1, 4]. With [3, 9, 6] and both limits equal to 6, every number becomes 6.
Approach
The work is split in two. A small helper method, clamp, deals with one number: it knows the rule. The method clampAll knows only how to go through the array and call the helper once per number. Each method then does one job, and each is easy to read and to test on its own.
Why split the work
The rule "move a number inside the limits" is one idea, and it has a name, clamp. Once it is a method of its own, the loop in clampAll reads like the task: for each number, add the clamped number. If the rule were wrong, you would fix it in one place. If you later needed to clamp a single number somewhere else, you could call the same method. The helper takes everything it needs as parameters (x, low and high) and gives back one answer, so you can check it alone before the loop exists.
Step by step
The helper
- If
xis less thanlow, returnlow. - If
xis greater thanhigh, returnhigh. - Otherwise return
x. Only a number between the limits reaches this line, so noelseis needed.
The main method
- Create an empty
ArrayBuffer[Int]calledclamped. It is aval, but the buffer can still grow. - Loop over the numbers with
for (num <- nums). - Append
clamp(num, low, high)toclampedwith+=. The loop needs noifof its own. - After the loop, return
clamped.
The solution in Scala
class Solution {
def clamp(x: Int, low: Int, high: Int): Int = {
if (x < low) {
return low
}
if (x > high) {
return high
}
return x
}
def clampAll(nums: Array[Int], low: Int, high: Int): ArrayBuffer[Int] = {
val clamped = ArrayBuffer[Int]()
for (num <- nums) {
clamped += clamp(num, low, high)
}
return clamped
}
}
Both methods live in the same class, so clampAll calls clamp by its plain name. The array and the buffer are different types: nums is a fixed-size Array, and the answer is an ArrayBuffer that grows one number at a time.
How the helper treats the first example
With limits 0 and 20, calling the helper on 12, -3, 7 and 25 returns 12, 0, 7 and 20. The first and third calls reach the final return x. The second stops at the first check, and the fourth stops at the second. Each number takes exactly one of the three exits.
Testing
The three examples return 12 0 7 20, -5 -1 4 and 6 6 6 (the tests print lists as values separated by spaces). The helper alone gives clamp(-3, 0, 20) = 0.
Another way
In Scala an if is an expression, so the helper can be written without return, as a chain whose last branch is the value:
def clamp(x: Int, low: Int, high: Int): Int =
if (x < low) low
else if (x > high) high
else x
It gives the same answers: on [12, -3, 7, 25] with limits 0 and 20 it produces 12 0 7 20. The lesson's version with early returns says the same thing one case at a time, which is easier to follow when you are starting out.
The loop could be replaced by nums.map(clamp(_, low, high)). Note that map on an Array gives back an Array, not an ArrayBuffer, and the lesson's method has to return a buffer. Wrapping the result with ArrayBuffer.from(...) converts it. The explicit loop avoids that detour and shows each step.
Edge cases and common mistakes
- Equal limits. When
lowandhighare the same, the first check catches numbers below it, the second catches numbers above it, and a number equal to both limits falls through and is returned as it is. - Limits in the wrong order. The statement promises
lowis never greater thanhigh. If it were, the two checks could disagree and the helper would no longer have a sensible answer, so the method does not try to handle it. - Negative limits. The comparisons work the same way: -8 is less than -5.
- Writing the comparisons in the loop. It works, but then the loop does the helper's job, and the point of the lesson is the split.
- Returning inside the loop. A
returnin the loop body would send back the first number alone. The buffer is returned after the loop. - Using
<=in the checks. It changes nothing in the result, because a number equal to a limit is returned as it is either way, but<and>match the rule as it is stated.
