Facebook Pixel

2726. Calculator with Method Chaining

Problem Description

This problem asks you to implement a Calculator class that supports basic mathematical operations and method chaining.

The Calculator class should:

  1. Initialize with a starting value: The constructor takes a number that becomes the initial result value.

  2. Support five mathematical operations, each returning the Calculator instance for chaining:

    • add(value): Adds value to the current result
    • subtract(value): Subtracts value from the current result
    • multiply(value): Multiplies the current result by value
    • divide(value): Divides the current result by value (must throw error "Division by zero is not allowed" if value is 0)
    • power(value): Raises the current result to the power of value
  3. Provide a way to retrieve the result: The getResult() method returns the current calculated value.

  4. Enable method chaining: All operation methods return this (the Calculator instance), allowing consecutive operations like:

    calculator.add(5).multiply(2).subtract(3).getResult()

The solution uses a private property x to store the current result. Each operation method modifies x and returns this to enable chaining. The divide method includes validation to prevent division by zero. The getResult method simply returns the current value of x.

Results within 10^-5 of the actual answer are considered correct, accounting for floating-point precision issues.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight for this problem is recognizing that method chaining requires each method to return the object itself. When we call calculator.add(5).multiply(2), the add(5) method must return the calculator object so that we can immediately call multiply(2) on it.

To achieve this pattern, we need:

  1. State preservation: A single variable to hold the running result throughout all operations. This is why we use a private property x to maintain the current value across method calls.

  2. Return this pattern: Each operation method performs its calculation on the stored value and then returns this (the current instance). This is the fundamental pattern that enables method chaining - by returning the object itself, we allow the next method to be called on the same object.

  3. Mutating operations: Instead of creating new Calculator instances, we modify the existing instance's state. This makes sense because we want a continuous calculation flow where each operation builds upon the previous result.

The design follows the Fluent Interface pattern, commonly used in builder patterns and query builders. The idea is to make the code read more naturally, almost like a sentence: "take this number, add 5, multiply by 2, subtract 3, and give me the result."

For the division by zero check, we handle this explicitly before performing the division since JavaScript would return Infinity for division by zero, which isn't the desired behavior. By throwing an error with the exact message required, we prevent invalid mathematical operations while maintaining the chainable interface (the error stops the chain execution).

The getResult() method breaks the chain by returning a number instead of the Calculator instance, which makes logical sense as it's the terminating operation to retrieve our final answer.

Solution Approach

The implementation uses a class-based approach with a single private property to maintain state and instance methods that return this for chaining.

Core Structure:

  1. Private property x: Stores the current calculation result. Using a private property ensures encapsulation and prevents external modification.

  2. Constructor: Initializes the x property with the provided starting value:

    constructor(value: number) {
        this.x = value;
    }

Implementation of Each Operation:

Each arithmetic method follows the same pattern:

  • Perform the operation on this.x
  • Return this to enable chaining
  1. Addition: this.x += value adds the value to the current result

  2. Subtraction: this.x -= value subtracts the value from the current result

  3. Multiplication: this.x *= value multiplies the current result by the value

  4. Division:

    divide(value: number): Calculator {
        if (value === 0) {
            throw new Error('Division by zero is not allowed');
        }
        this.x /= value;
        return this;
    }

    First checks for zero to prevent invalid operations, then performs this.x /= value

  5. Power: this.x **= value raises the current result to the given power using the exponentiation operator

Result Retrieval: The getResult() method simply returns the current value of x without modifying it:

getResult(): number {
    return this.x;
}

Method Chaining Mechanism: By returning this from each operation method, we enable consecutive method calls:

new Calculator(10)
    .add(5)      // x becomes 15, returns this
    .multiply(2) // x becomes 30, returns this
    .divide(3)   // x becomes 10, returns this
    .getResult() // returns 10

The solution is straightforward and efficient with O(1) time complexity for each operation and O(1) space complexity for the entire calculator instance.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a calculation step-by-step: new Calculator(2).add(5).multiply(3).subtract(4).divide(2).getResult()

Step 1: Initialize

new Calculator(2)
  • Creates a new Calculator instance
  • Sets internal property x = 2
  • Current state: x = 2

Step 2: Add 5

.add(5)
  • Executes: this.x = this.x + 5 = 2 + 5 = 7
  • Returns this (the Calculator instance)
  • Current state: x = 7

Step 3: Multiply by 3

.multiply(3)
  • Executes: this.x = this.x * 3 = 7 * 3 = 21
  • Returns this (the Calculator instance)
  • Current state: x = 21

Step 4: Subtract 4

.subtract(4)
  • Executes: this.x = this.x - 4 = 21 - 4 = 17
  • Returns this (the Calculator instance)
  • Current state: x = 17

Step 5: Divide by 2

.divide(2)
  • First checks: Is value === 0? No, proceed
  • Executes: this.x = this.x / 2 = 17 / 2 = 8.5
  • Returns this (the Calculator instance)
  • Current state: x = 8.5

Step 6: Get Result

.getResult()
  • Returns the current value of x
  • Final answer: 8.5

Key Points:

  • Each operation method modifies the same internal variable x
  • Each operation returns this, allowing the next method to be called immediately
  • The chain continues until getResult() is called, which returns a number instead of the Calculator instance
  • If we tried .divide(0) at any point, it would throw an error and stop execution

Solution Implementation

1# Global variable to store the current calculator value
2calculator_value = 0
3
4def initialize_calculator(value):
5    """
6    Initializes the calculator with a starting value
7  
8    Args:
9        value: The initial value for the calculator
10    """
11    global calculator_value
12    calculator_value = value
13
14def add(value):
15    """
16    Adds a value to the current calculator value
17  
18    Args:
19        value: The value to add
20  
21    Returns:
22        The updated calculator value
23    """
24    global calculator_value
25    calculator_value += value
26    return calculator_value
27
28def subtract(value):
29    """
30    Subtracts a value from the current calculator value
31  
32    Args:
33        value: The value to subtract
34  
35    Returns:
36        The updated calculator value
37    """
38    global calculator_value
39    calculator_value -= value
40    return calculator_value
41
42def multiply(value):
43    """
44    Multiplies the current calculator value by a given value
45  
46    Args:
47        value: The value to multiply by
48  
49    Returns:
50        The updated calculator value
51    """
52    global calculator_value
53    calculator_value *= value
54    return calculator_value
55
56def divide(value):
57    """
58    Divides the current calculator value by a given value
59  
60    Args:
61        value: The value to divide by
62  
63    Returns:
64        The updated calculator value
65  
66    Raises:
67        ValueError: If attempting to divide by zero
68    """
69    global calculator_value
70    if value == 0:
71        raise ValueError('Division by zero is not allowed')
72    calculator_value /= value
73    return calculator_value
74
75def power(value):
76    """
77    Raises the current calculator value to a given power
78  
79    Args:
80        value: The exponent value
81  
82    Returns:
83        The updated calculator value
84    """
85    global calculator_value
86    calculator_value **= value
87    return calculator_value
88
89def get_result():
90    """
91    Gets the current result of the calculator
92  
93    Returns:
94        The current calculator value
95    """
96    return calculator_value
97
1/**
2 * Calculator class that maintains a global state for calculator operations
3 */
4public class Calculator {
5  
6    // Global variable to store the current calculator value
7    private static double calculatorValue = 0;
8  
9    /**
10     * Initializes the calculator with a starting value
11     * @param value The initial value for the calculator
12     */
13    public static void initializeCalculator(double value) {
14        calculatorValue = value;
15    }
16  
17    /**
18     * Adds a value to the current calculator value
19     * @param value The value to add
20     * @return The updated calculator value
21     */
22    public static double add(double value) {
23        calculatorValue += value;
24        return calculatorValue;
25    }
26  
27    /**
28     * Subtracts a value from the current calculator value
29     * @param value The value to subtract
30     * @return The updated calculator value
31     */
32    public static double subtract(double value) {
33        calculatorValue -= value;
34        return calculatorValue;
35    }
36  
37    /**
38     * Multiplies the current calculator value by a given value
39     * @param value The value to multiply by
40     * @return The updated calculator value
41     */
42    public static double multiply(double value) {
43        calculatorValue *= value;
44        return calculatorValue;
45    }
46  
47    /**
48     * Divides the current calculator value by a given value
49     * @param value The value to divide by
50     * @return The updated calculator value
51     * @throws IllegalArgumentException if attempting to divide by zero
52     */
53    public static double divide(double value) {
54        if (value == 0) {
55            throw new IllegalArgumentException("Division by zero is not allowed");
56        }
57        calculatorValue /= value;
58        return calculatorValue;
59    }
60  
61    /**
62     * Raises the current calculator value to a given power
63     * @param value The exponent value
64     * @return The updated calculator value
65     */
66    public static double power(double value) {
67        calculatorValue = Math.pow(calculatorValue, value);
68        return calculatorValue;
69    }
70  
71    /**
72     * Gets the current result of the calculator
73     * @return The current calculator value
74     */
75    public static double getResult() {
76        return calculatorValue;
77    }
78}
79
1#include <stdexcept>
2#include <cmath>
3
4// Global variable to store the current calculator value
5double calculatorValue = 0.0;
6
7/**
8 * Initializes the calculator with a starting value
9 * @param value - The initial value for the calculator
10 */
11void initializeCalculator(double value) {
12    calculatorValue = value;
13}
14
15/**
16 * Adds a value to the current calculator value
17 * @param value - The value to add
18 * @return The updated calculator value
19 */
20double add(double value) {
21    calculatorValue += value;
22    return calculatorValue;
23}
24
25/**
26 * Subtracts a value from the current calculator value
27 * @param value - The value to subtract
28 * @return The updated calculator value
29 */
30double subtract(double value) {
31    calculatorValue -= value;
32    return calculatorValue;
33}
34
35/**
36 * Multiplies the current calculator value by a given value
37 * @param value - The value to multiply by
38 * @return The updated calculator value
39 */
40double multiply(double value) {
41    calculatorValue *= value;
42    return calculatorValue;
43}
44
45/**
46 * Divides the current calculator value by a given value
47 * @param value - The value to divide by
48 * @return The updated calculator value
49 * @throws std::invalid_argument if attempting to divide by zero
50 */
51double divide(double value) {
52    if (value == 0.0) {
53        throw std::invalid_argument("Division by zero is not allowed");
54    }
55    calculatorValue /= value;
56    return calculatorValue;
57}
58
59/**
60 * Raises the current calculator value to a given power
61 * @param value - The exponent value
62 * @return The updated calculator value
63 */
64double power(double value) {
65    calculatorValue = std::pow(calculatorValue, value);
66    return calculatorValue;
67}
68
69/**
70 * Gets the current result of the calculator
71 * @return The current calculator value
72 */
73double getResult() {
74    return calculatorValue;
75}
76
1// Global variable to store the current calculator value
2let calculatorValue: number = 0;
3
4/**
5 * Initializes the calculator with a starting value
6 * @param value - The initial value for the calculator
7 */
8function initializeCalculator(value: number): void {
9    calculatorValue = value;
10}
11
12/**
13 * Adds a value to the current calculator value
14 * @param value - The value to add
15 * @returns The updated calculator value
16 */
17function add(value: number): number {
18    calculatorValue += value;
19    return calculatorValue;
20}
21
22/**
23 * Subtracts a value from the current calculator value
24 * @param value - The value to subtract
25 * @returns The updated calculator value
26 */
27function subtract(value: number): number {
28    calculatorValue -= value;
29    return calculatorValue;
30}
31
32/**
33 * Multiplies the current calculator value by a given value
34 * @param value - The value to multiply by
35 * @returns The updated calculator value
36 */
37function multiply(value: number): number {
38    calculatorValue *= value;
39    return calculatorValue;
40}
41
42/**
43 * Divides the current calculator value by a given value
44 * @param value - The value to divide by
45 * @returns The updated calculator value
46 * @throws Error if attempting to divide by zero
47 */
48function divide(value: number): number {
49    if (value === 0) {
50        throw new Error('Division by zero is not allowed');
51    }
52    calculatorValue /= value;
53    return calculatorValue;
54}
55
56/**
57 * Raises the current calculator value to a given power
58 * @param value - The exponent value
59 * @returns The updated calculator value
60 */
61function power(value: number): number {
62    calculatorValue **= value;
63    return calculatorValue;
64}
65
66/**
67 * Gets the current result of the calculator
68 * @returns The current calculator value
69 */
70function getResult(): number {
71    return calculatorValue;
72}
73

Time and Space Complexity

Time Complexity: Each method (add, subtract, multiply, divide, power, getResult) performs a constant-time operation. The arithmetic operations and the assignment take O(1) time. The constructor also runs in O(1) time as it only initializes a single variable. Therefore, each individual method call has a time complexity of O(1).

For a chain of n method calls (e.g., calculator.add(5).multiply(2).subtract(3)...), the total time complexity would be O(n), where n is the number of operations performed.

Space Complexity: The Calculator class stores only a single number x as its state, regardless of how many operations are performed. The space used remains constant throughout the lifetime of the object. Each method returns this (a reference to the same object), so no additional Calculator objects are created during method chaining. Therefore, the space complexity is O(1).

The method chaining pattern (fluent interface) doesn't create new objects but modifies and returns the same instance, maintaining constant space usage.

Common Pitfalls

1. Global State Management Issues

The Python implementation uses a global variable calculator_value, which creates several problems:

  • No support for multiple calculator instances: You can't have two calculators running independently
  • State persistence between tests: The global value persists across different test cases, leading to incorrect results
  • Thread safety concerns: Global state is not thread-safe for concurrent operations

Example of the problem:

initialize_calculator(10)
add(5)  # calculator_value = 15
# Start a "new" calculator
initialize_calculator(20)
# But if someone forgets to initialize, they get the old value!

2. Loss of Method Chaining

The provided Python solution doesn't support method chaining as required by the problem. You can't write:

# This won't work with the global function approach
result = Calculator(10).add(5).multiply(2).getResult()

3. Floating-Point Precision Issues

When performing division and power operations, floating-point arithmetic can introduce precision errors that might exceed the 10^-5 tolerance:

# Example: 0.1 + 0.2 != 0.3 in floating-point
calculator_value = 0.1
add(0.2)  # Might not exactly equal 0.3

Solution

Here's a proper class-based implementation that addresses these pitfalls:

class Calculator:
    def __init__(self, value: float):
        """Initialize calculator with a starting value"""
        self._result = float(value)  # Ensure float for consistency
  
    def add(self, value: float):
        """Add value to current result"""
        self._result += value
        return self  # Enable chaining
  
    def subtract(self, value: float):
        """Subtract value from current result"""
        self._result -= value
        return self
  
    def multiply(self, value: float):
        """Multiply current result by value"""
        self._result *= value
        return self
  
    def divide(self, value: float):
        """Divide current result by value"""
        if value == 0:
            raise ValueError('Division by zero is not allowed')
        self._result /= value
        return self
  
    def power(self, value: float):
        """Raise current result to the power of value"""
        self._result **= value
        return self
  
    def getResult(self) -> float:
        """Get the current result"""
        # Round to handle floating-point precision within 10^-5
        return round(self._result, 10)

# Usage example with proper chaining:
calc = Calculator(10)
result = calc.add(5).multiply(2).divide(3).getResult()
print(result)  # Output: 10.0

# Multiple independent instances work correctly:
calc1 = Calculator(10)
calc2 = Calculator(20)
calc1.add(5)
calc2.subtract(5)
print(calc1.getResult())  # 15.0
print(calc2.getResult())  # 15.0

Key improvements:

  1. Instance-based state: Each Calculator object has its own _result property
  2. Method chaining support: All operation methods return self
  3. Proper encapsulation: Using _result as a private property
  4. Floating-point handling: Converting input to float and rounding output for precision
  5. Multiple instances: Can create and use multiple calculators independently
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does quick sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More