LeetCode 2726 - Calculator with Method Chaining
This problem asks us to design a class named Calculator that supports basic arithmetic operations while enabling method chaining. Method chaining means that after calling one method, we can immediately call another method on the same object.
Difficulty: 🟢 Easy
Topics: —
Solution
Problem Understanding
This problem asks us to design a class named Calculator that supports basic arithmetic operations while enabling method chaining. Method chaining means that after calling one method, we can immediately call another method on the same object. For example:
new Calculator(10).add(5).subtract(7).getResult()
works because each mathematical method returns the same Calculator instance after updating its internal state.
The class constructor initializes a variable called result, which stores the current value of the calculator. Every operation modifies this stored value in place.
The required methods are:
add(value)addsvaluetoresultsubtract(value)subtractsvaluefromresultmultiply(value)multipliesresultbyvaluedivide(value)dividesresultbyvaluepower(value)raisesresultto the given exponentgetResult()returns the final value
The important detail is that all arithmetic methods must return the updated calculator instance, not the numeric result. This is what enables chaining.
The only exceptional case is division by zero. If divide(0) is called, the implementation must throw an error with the exact message:
"Division by zero is not allowed"
The input representation in the examples uses two arrays:
actions, which describes the sequence of operationsvalues, which provides arguments for those operations
However, on LeetCode, we are only required to implement the Calculator class. The testing system will instantiate and invoke methods automatically.
The constraints show that there can be up to 2 × 10^4 operations. This tells us that our implementation must be efficient and avoid unnecessary overhead. Fortunately, each operation only updates a single number, so an O(1) implementation per method is sufficient.
Several edge cases are important:
- Division by zero, which must raise an error instead of returning a result.
- Negative numbers, since arithmetic operations should work correctly with signed values.
- Exponentiation with zero or negative exponents, which should rely on normal mathematical behavior.
- Floating-point precision, because division and exponentiation may produce decimal values. The problem allows answers within
10^-5of the expected result.
Approaches
Brute Force Approach
A brute-force way to think about this problem is to maintain a history of every operation and recompute the result from scratch every time a new method is called.
For example, if we start with 10, then call:
add(5)
subtract(2)
multiply(3)
we could store these operations in a list and recalculate the entire sequence whenever getResult() is called.
This approach is correct because replaying all operations guarantees the final result reflects every modification. However, it is unnecessarily inefficient. If there are n operations, recomputing from the beginning each time may require O(n) work per method call, which is wasteful.
Since the calculator only needs the current result, storing the full operation history is unnecessary.
Optimal Approach
The key observation is that each operation only depends on the current result, not the full history of previous operations.
Instead of saving operations, we can maintain a single variable, result, and update it directly whenever a method is called.
For example:
result = 10
add(5) → result = 15
subtract(2) → result = 13
multiply(3) → result = 39
Each operation takes constant time because we only modify one number.
To enable method chaining, every arithmetic method returns self in Python or the calculator pointer in Go. This allows calls like:
calculator.add(5).multiply(2).power(3)
without creating new objects.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) per operation | O(n) | Stores operation history and recomputes repeatedly |
| Optimal | O(1) per operation | O(1) | Updates a single running result directly |
Algorithm Walkthrough
- Create a
Calculatorclass with a constructor that stores the initial value in an instance variable calledresult. - Implement the
add(value)method. Increaseresultbyvalue, then return the calculator object itself. Returning the same object allows chaining. - Implement the
subtract(value)method. Decreaseresultbyvalue, then return the calculator object. - Implement the
multiply(value)method. Multiplyresultbyvalue, then return the calculator object. - Implement the
divide(value)method. Before dividing, check whethervalueequals0. If it does, throw an exception with the required message. Otherwise, divideresultbyvalueand return the calculator object. - Implement the
power(value)method. Raiseresultto the exponentvalue, then return the calculator object. - Implement
getResult(). Return the current value stored inresult.
Why it works
The correctness comes from maintaining the invariant that result always represents the outcome of all previously applied operations.
Initially, result equals the constructor value. Each method modifies result exactly according to its mathematical definition. Since every operation updates the same running value and no operation is skipped, getResult() always returns the mathematically correct final answer.
Python Solution
class Calculator:
def __init__(self, value: float):
self.result = value
def add(self, value: float) -> "Calculator":
self.result += value
return self
def subtract(self, value: float) -> "Calculator":
self.result -= value
return self
def multiply(self, value: float) -> "Calculator":
self.result *= value
return self
def divide(self, value: float) -> "Calculator":
if value == 0:
raise Exception("Division by zero is not allowed")
self.result /= value
return self
def power(self, value: float) -> "Calculator":
self.result **= value
return self
def getResult(self) -> float:
return self.result
The implementation follows the algorithm directly. The constructor initializes the internal result variable. Every arithmetic method updates this variable in constant time.
The important implementation detail is returning self from every mathematical operation. Without this, method chaining would fail because subsequent calls would not have a calculator object to operate on.
The divide method includes a guard clause that checks for division by zero before performing the operation. If zero is detected, an exception with the exact required message is raised immediately.
Finally, getResult() simply returns the current stored value.
Go Solution
type Calculator struct {
result float64
}
func Constructor(value float64) Calculator {
return Calculator{
result: value,
}
}
func (this *Calculator) Add(value float64) *Calculator {
this.result += value
return this
}
func (this *Calculator) Subtract(value float64) *Calculator {
this.result -= value
return this
}
func (this *Calculator) Multiply(value float64) *Calculator {
this.result *= value
return this
}
func (this *Calculator) Divide(value float64) *Calculator {
if value == 0 {
panic("Division by zero is not allowed")
}
this.result /= value
return this
}
func (this *Calculator) Power(value float64) *Calculator {
this.result = math.Pow(this.result, value)
return this
}
func (this *Calculator) GetResult() float64 {
return this.result
}
/**
* Your Calculator object will be instantiated and called as such:
* obj := Constructor(value);
* param_1 := obj.Add(value);
* param_2 := obj.Subtract(value);
* param_3 := obj.Multiply(value);
* param_4 := obj.Divide(value);
* param_5 := obj.Power(value);
* param_6 := obj.GetResult();
*/
The Go version uses pointer receivers (*Calculator) so methods modify the same calculator instance instead of copying the struct.
Unlike Python, Go does not have a built-in exponent operator, so exponentiation uses math.Pow. This requires importing the math package.
Division by zero is handled with panic, which aligns with the requirement to throw an error.
Worked Examples
Example 1
Input
actions = ["Calculator", "add", "subtract", "getResult"]
values = [10, 5, 7]
Execution:
new Calculator(10).add(5).subtract(7).getResult()
| Step | Operation | Result |
|---|---|---|
| 1 | Initialize Calculator(10) | 10 |
| 2 | add(5) | 15 |
| 3 | subtract(7) | 8 |
| 4 | getResult() | 8 |
Final output:
8
Example 2
Input
actions = ["Calculator", "multiply", "power", "getResult"]
values = [2, 5, 2]
Execution:
new Calculator(2).multiply(5).power(2).getResult()
| Step | Operation | Result |
|---|---|---|
| 1 | Initialize Calculator(2) | 2 |
| 2 | multiply(5) | 10 |
| 3 | power(2) | 100 |
| 4 | getResult() | 100 |
Final output:
100
Example 3
Input
actions = ["Calculator", "divide", "getResult"]
values = [20, 0]
Execution:
new Calculator(20).divide(0).getResult()
| Step | Operation | Result |
|---|---|---|
| 1 | Initialize Calculator(20) | 20 |
| 2 | divide(0) | Error thrown |
Since division by zero is invalid, execution stops immediately and throws:
"Division by zero is not allowed"
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(1) per operation | Every method updates one variable in constant time |
| Space | O(1) | Only one internal value is stored |
The implementation is extremely efficient because no additional data structures are required. Each operation performs only a constant number of arithmetic operations and modifies the same stored result. Memory usage remains constant regardless of how many operations are chained.
Test Cases
# Example 1
calc = Calculator(10)
assert calc.add(5).subtract(7).getResult() == 8 # Basic add and subtract chain
# Example 2
calc = Calculator(2)
assert calc.multiply(5).power(2).getResult() == 100 # Multiply then exponentiation
# Example 3
calc = Calculator(20)
try:
calc.divide(0)
assert False, "Expected exception"
except Exception as e:
assert str(e) == "Division by zero is not allowed" # Division by zero
# Single operation
calc = Calculator(5)
assert calc.getResult() == 5 # No operations applied
# Negative numbers
calc = Calculator(-10)
assert calc.add(5).multiply(2).getResult() == -10 # Negative arithmetic
# Decimal division
calc = Calculator(10)
assert abs(calc.divide(4).getResult() - 2.5) < 1e-5 # Floating point division
# Power with zero exponent
calc = Calculator(7)
assert calc.power(0).getResult() == 1 # x^0 = 1
# Chained operations
calc = Calculator(3)
assert calc.add(2).multiply(4).subtract(5).divide(3).getResult() == 5 # Long chain
# Zero initial value
calc = Calculator(0)
assert calc.add(10).getResult() == 10 # Start from zero
# Negative exponent
calc = Calculator(2)
assert abs(calc.power(-1).getResult() - 0.5) < 1e-5 # Reciprocal power
| Test | Why |
|---|---|
Calculator(10).add(5).subtract(7) |
Validates basic chaining |
multiply().power() |
Ensures multiplication and exponentiation work |
divide(0) |
Confirms required exception behavior |
getResult() only |
Validates constructor initialization |
| Negative values | Ensures signed arithmetic correctness |
| Decimal division | Verifies floating-point precision |
Power with exponent 0 |
Tests mathematical identity |
| Long method chain | Ensures chaining reliability |
| Zero initial value | Confirms initialization edge case |
| Negative exponent | Verifies fractional outputs |
Edge Cases
Division by Zero
The most important edge case is division by zero. A naive implementation might attempt the division directly, causing runtime errors or undefined behavior depending on the language. The implementation explicitly checks for value == 0 before division and immediately raises an exception with the exact required message.
Floating-Point Results
Operations like division and exponentiation can produce decimal numbers even if the inputs are integers. For example:
Calculator(10).divide(4)
produces 2.5. The implementation naturally handles floating-point arithmetic and the problem allows answers within 10^-5, which prevents precision issues from causing failures.
Long Method Chains
Since the constraints allow up to 2 × 10^4 operations, inefficient implementations could become slow or memory-heavy. For example:
Calculator(1).add(1).add(1)... repeated many times
The implementation avoids storing operation history and instead updates a single running value, ensuring constant memory usage and efficient execution even for very long chains.
Negative and Zero Exponents
Exponentiation can introduce subtle cases such as:
Calculator(5).power(0)
which should return 1, or:
Calculator(2).power(-1)
which should return 0.5. By relying on the language's built-in exponentiation behavior, these mathematical edge cases are handled correctly without extra special-case logic.