Taro Logo

Allow One Function Call

Easy
Asked by:
Profile picture
Profile picture
Profile picture
Profile picture
29 views

Given a function fn, return a new function that is identical to the original function except that it ensures fn is called at most once.

  • The first time the returned function is called, it should return the same result as fn.
  • Every subsequent time it is called, it should return undefined.

Example 1:

Input: fn = (a,b,c) => (a + b + c), calls = [[1,2,3],[2,3,6]]
Output: [{"calls":1,"value":6}]
Explanation:
const onceFn = once(fn);
onceFn(1, 2, 3); // 6
onceFn(2, 3, 6); // undefined, fn was not called

Example 2:

Input: fn = (a,b,c) => (a * b * c), calls = [[5,7,4],[2,3,6],[4,6,8]]
Output: [{"calls":1,"value":140}]
Explanation:
const onceFn = once(fn);
onceFn(5, 7, 4); // 140
onceFn(2, 3, 6); // undefined, fn was not called
onceFn(4, 6, 8); // undefined, fn was not called

Constraints:

  • calls is a valid JSON array
  • 1 <= calls.length <= 10
  • 1 <= calls[i].length <= 100
  • 2 <= JSON.stringify(calls).length <= 1000

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What is the intended behavior if the function is called more than once? Should it throw an error, return a specific value, or have no effect?
  2. What data type will the function argument be, and are there any constraints on the value it can hold (e.g., null, undefined, specific range of numbers, empty strings)?
  3. If the function relies on external state or variables, is it safe to assume they will remain unchanged between multiple potential calls?
  4. Does the function need to be idempotent, meaning calling it multiple times with the same argument should have the same effect as calling it once?
  5. Should the function return any value, and if so, what should it return on the initial call and on subsequent calls?

Brute Force Solution

Approach

The challenge is to make sure a function is only ever called once. The brute force method is all about initially assuming the function can be called, and then creating a mechanism to prevent it from being called a second time.

Here's how the algorithm would work step-by-step:

  1. First, we start with a mechanism that allows the function to be called.
  2. The first time the function is called, we need to record that it has been called.
  3. We can achieve this by using a variable that acts as a flag or a switch.
  4. After the first call, we change the state of that flag.
  5. Before any future calls, we check the flag. If the flag indicates it has already been called, we do nothing or return a special value to indicate an error.
  6. This way, every time the function is invoked, we ensure it is only actually executed if it hasn't been executed before.

Code Implementation

def allow_one_function_call(function):
    function_has_been_called = False

    def wrapper(*args, **kwargs):
        nonlocal function_has_been_called

        # Check if the function has already been called.
        if function_has_been_called:
            return None

        # Mark the function as called before execution.
        function_has_been_called = True

        # Execute the original function.
        result = function(*args, **kwargs)

        return result

    return wrapper

Big(O) Analysis

Time Complexity
O(1)The provided solution focuses on ensuring a function is called only once, which primarily involves checking a flag variable. This check and potential function execution happen a fixed number of times regardless of any input size. Therefore, the time complexity is constant, denoted as O(1).
Space Complexity
O(1)The described solution utilizes a flag variable to track whether the function has been called. This flag occupies a constant amount of space regardless of any input size, N. No other data structures are used for storing intermediate results or managing recursive calls. Therefore, the auxiliary space complexity is constant.

Optimal Solution

Approach

The goal is to ensure a function can only be executed once, no matter how many times it's called. We achieve this by using a flag to track if the function has already run. If it has, we simply return; otherwise, we execute the function and set the flag.

Here's how the algorithm would work step-by-step:

  1. Create a variable outside the function to act as a switch. Initially, this switch should be in the 'off' position, signaling that the function hasn't run yet.
  2. Inside the function, first check the position of the switch. If it's 'on', it means the function has already been executed, so immediately stop and do nothing more.
  3. If the switch is 'off', it means this is the first time the function is being called. Execute the main tasks of the function.
  4. Immediately after the main tasks are completed, flip the switch to the 'on' position, indicating that the function has now been executed.
  5. Now, every subsequent call to the function will find the switch in the 'on' position and will immediately stop, ensuring the function's main tasks only run once.

Code Implementation

already_executed = False

def allow_one_function_call(function):
    def wrapper(*args, **kwargs):
        global already_executed

        # Check if the function has already been executed.
        if already_executed:
            return

        # Execute the function if it hasn't run yet.
        result = function(*args, **kwargs)

        # Set the flag to indicate the function has been executed.
        already_executed = True

        return result

    return wrapper

Big(O) Analysis

Time Complexity
O(1)The function performs a constant number of operations: a boolean check and potentially executing the provided function and setting the boolean flag. The number of operations does not scale with any input size, therefore the time complexity is O(1). Regardless of how many times the function is called, the operations inside only involve a conditional check and assignment which take constant time. Subsequent calls after the first execution will only perform the boolean check and return, also in constant time.
Space Complexity
O(1)The provided solution uses a single boolean variable (the 'switch') to track whether the function has been executed. This variable requires a constant amount of memory, irrespective of the input size. No other data structures or variables are created that depend on the input. Therefore, the auxiliary space complexity is O(1).

Edge Cases

Null or undefined function as input
How to Handle:
Throw an error or return a predefined value indicating invalid input.
Function throws an error during its execution
How to Handle:
Catch the error and return undefined (or a specific error value), preventing program termination.
Function takes no arguments
How to Handle:
The wrapper should call the function without any arguments.
Function has already been called
How to Handle:
The function should return undefined if called more than once, as the 'called' flag prevents re-execution.
Calling returned function multiple times.
How to Handle:
The returned function maintains state (closure over 'called'), and subsequent calls after the first return undefined.
Function returns null or undefined.
How to Handle:
The wrapper correctly handles null or undefined return values, ensuring they are returned only on the initial execution.
Function contains side effects (e.g., modifying global variables).
How to Handle:
The wrapper cannot prevent the side effects of the function, it can only control its execution count.
Function with multiple arguments.
How to Handle:
The wrapper should accept and pass any number of arguments to the original function.