2797. Partial Function with Placeholders
Problem Description
This problem asks you to implement a partial function application mechanism in JavaScript/TypeScript.
You need to create a function partial that takes two parameters:
fn: A function to be partially appliedargs: An array of arguments that may contain placeholders
The partial function should return a new function partialFn that:
-
Replaces placeholders: When
partialFnis called withrestArgs, it should replace any"_"placeholders in the originalargsarray with values fromrestArgsin order. For example, ifargs = [1, "_", 3, "_"]andrestArgs = [2, 4], the placeholders get replaced to form[1, 2, 3, 4]. -
Appends remaining arguments: If there are more values in
restArgsthan there are placeholders, the extra values should be appended to the end of the arguments list. For example, ifargs = [1, "_"]andrestArgs = [2, 3, 4], the final arguments become[1, 2, 3, 4]. -
Calls the original function: Finally,
partialFnshould call the original functionfnwith the modified arguments array spread as individual parameters and return its result.
Example:
function add(a, b, c) { return a + b + c; } const partialAdd = partial(add, [1, "_", 3]); partialAdd(2); // Returns 6 (calls add(1, 2, 3)) const partialAdd2 = partial(add, ["_", "_"]); partialAdd2(1, 2, 3, 4); // Returns 6 (calls add(1, 2, 3, 4), extra argument 4 is passed but ignored by add)
The key challenge is to correctly track and replace placeholders while maintaining the order of arguments and handling any excess arguments appropriately.
Intuition
The key insight is that we need to create a closure that "remembers" the original function and arguments template, then fills in the blanks when the returned function is called.
Think of it like a form with some fields already filled in and some blank spaces marked with "_". When someone provides the missing information (restArgs), we need to:
- Fill in the blanks in order
- Add any extra information at the end
The natural approach is to iterate through the original args array and whenever we encounter a placeholder "_", we replace it with the next available value from restArgs. We need a pointer (i) to track which value from restArgs we should use next.
Here's the step-by-step thought process:
-
Two-pointer technique: We need one pointer (
j) to traverse theargsarray and another pointer (i) to track our position inrestArgs. This ensures we fill placeholders in the correct order. -
In-place replacement: When we find a
"_"inargs[j], we replace it withrestArgs[i]and incrementito move to the next replacement value. -
Handle excess arguments: After replacing all placeholders, if we still have unused values in
restArgs(wheni < restArgs.length), these should be appended to the end. This allows for functions that can accept variable numbers of arguments. -
Spread operator for function call: Finally, we use the spread operator
fn(...args)to pass the modified array as individual arguments to the original function, which is exactly how the original function expects to receive them.
The beauty of this solution is its simplicity - it modifies the args array directly during execution, making the logic straightforward and efficient with O(n) time complexity where n is the length of the arguments.
Solution Implementation
1def partial(fn, args): 2 """ 3 Creates a partially applied function with placeholder support 4 5 Args: 6 fn: The function to be partially applied 7 args: Initial arguments, where '_' acts as a placeholder for future arguments 8 9 Returns: 10 A new function that accepts remaining arguments 11 """ 12 def wrapper(*rest_args): 13 # Clone the args list to avoid mutating the original 14 final_args = args.copy() 15 rest_args_index = 0 16 17 # Replace placeholders ('_') with arguments from rest_args 18 for i in range(len(final_args)): 19 if final_args[i] == '_': 20 # Check if there are still rest_args available 21 if rest_args_index < len(rest_args): 22 final_args[i] = rest_args[rest_args_index] 23 rest_args_index += 1 24 25 # Append any remaining arguments that weren't used for placeholders 26 while rest_args_index < len(rest_args): 27 final_args.append(rest_args[rest_args_index]) 28 rest_args_index += 1 29 30 # Call the original function with the complete argument list 31 return fn(*final_args) 32 33 return wrapper 341import java.util.ArrayList; 2import java.util.Arrays; 3import java.util.List; 4import java.util.function.Function; 5 6/** 7 * Creates a partially applied function with placeholder support. 8 * This implementation uses a functional interface to represent the partially applied function. 9 */ 10public class PartialFunction { 11 12 // Placeholder constant for argument positions that will be filled later 13 private static final Object PLACEHOLDER = "_"; 14 15 /** 16 * Creates a partially applied function with placeholder support 17 * @param fn - The function to be partially applied 18 * @param args - Initial arguments, where "_" acts as a placeholder for future arguments 19 * @return A new function that accepts remaining arguments 20 */ 21 public static Function<Object[], Object> partial(Function<Object[], Object> fn, Object[] args) { 22 return (Object[] restArgs) -> { 23 // Clone the args array to avoid mutating the original 24 List<Object> finalArgs = new ArrayList<>(Arrays.asList(args)); 25 int restArgsIndex = 0; 26 27 // Replace placeholders ('_') with arguments from restArgs 28 for (int i = 0; i < finalArgs.size(); i++) { 29 if (PLACEHOLDER.equals(finalArgs.get(i))) { 30 // Check if there are remaining arguments to use 31 if (restArgsIndex < restArgs.length) { 32 finalArgs.set(i, restArgs[restArgsIndex]); 33 restArgsIndex++; 34 } 35 } 36 } 37 38 // Append any remaining arguments that weren't used for placeholders 39 while (restArgsIndex < restArgs.length) { 40 finalArgs.add(restArgs[restArgsIndex]); 41 restArgsIndex++; 42 } 43 44 // Call the original function with the complete argument list 45 return fn.apply(finalArgs.toArray()); 46 }; 47 } 48} 491#include <functional> 2#include <vector> 3#include <any> 4#include <string> 5 6/** 7 * Creates a partially applied function with placeholder support 8 * @param fn - The function to be partially applied 9 * @param args - Initial arguments, where "_" acts as a placeholder for future arguments 10 * @returns A new function that accepts remaining arguments 11 */ 12template<typename ReturnType, typename... FnArgs> 13std::function<ReturnType(const std::vector<std::any>&)> partial( 14 std::function<ReturnType(FnArgs...)> fn, 15 const std::vector<std::any>& args) { 16 17 // Return a lambda that captures fn and args 18 return [fn, args](const std::vector<std::any>& restArgs) -> ReturnType { 19 // Clone the args vector to avoid mutating the original 20 std::vector<std::any> finalArgs = args; 21 size_t restArgsIndex = 0; 22 23 // Replace placeholders ('_') with arguments from restArgs 24 for (size_t i = 0; i < finalArgs.size(); i++) { 25 // Check if current element is a placeholder 26 if (finalArgs[i].has_value() && 27 finalArgs[i].type() == typeid(std::string)) { 28 try { 29 std::string val = std::any_cast<std::string>(finalArgs[i]); 30 if (val == "_") { 31 // Replace placeholder with next available argument 32 if (restArgsIndex < restArgs.size()) { 33 finalArgs[i] = restArgs[restArgsIndex++]; 34 } 35 } 36 } catch (const std::bad_any_cast& e) { 37 // Not a string, skip 38 } 39 } 40 } 41 42 // Append any remaining arguments that weren't used for placeholders 43 while (restArgsIndex < restArgs.size()) { 44 finalArgs.push_back(restArgs[restArgsIndex++]); 45 } 46 47 // Convert vector of any to tuple and call the original function 48 // Note: This requires helper function to unpack vector to function arguments 49 return applyFunction(fn, finalArgs); 50 }; 51} 52 53// Helper function to apply function with vector of arguments 54template<typename ReturnType, typename... Args, size_t... Is> 55ReturnType applyFunctionImpl( 56 std::function<ReturnType(Args...)> fn, 57 const std::vector<std::any>& args, 58 std::index_sequence<Is...>) { 59 // Unpack vector elements and cast to appropriate types 60 return fn(std::any_cast<Args>(args[Is])...); 61} 62 63template<typename ReturnType, typename... Args> 64ReturnType applyFunction( 65 std::function<ReturnType(Args...)> fn, 66 const std::vector<std::any>& args) { 67 return applyFunctionImpl(fn, args, std::index_sequence_for<Args...>{}); 68} 691/** 2 * Creates a partially applied function with placeholder support 3 * @param fn - The function to be partially applied 4 * @param args - Initial arguments, where '_' acts as a placeholder for future arguments 5 * @returns A new function that accepts remaining arguments 6 */ 7function partial(fn: Function, args: any[]): Function { 8 return function (...restArgs: any[]): any { 9 // Clone the args array to avoid mutating the original 10 const finalArgs: any[] = [...args]; 11 let restArgsIndex: number = 0; 12 13 // Replace placeholders ('_') with arguments from restArgs 14 for (let i = 0; i < finalArgs.length; i++) { 15 if (finalArgs[i] === '_') { 16 finalArgs[i] = restArgs[restArgsIndex++]; 17 } 18 } 19 20 // Append any remaining arguments that weren't used for placeholders 21 while (restArgsIndex < restArgs.length) { 22 finalArgs.push(restArgs[restArgsIndex++]); 23 } 24 25 // Call the original function with the complete argument list 26 return fn(...finalArgs); 27 }; 28} 29Solution Approach
The implementation uses a closure pattern to create a function that captures both the original function fn and the argument template args.
Here's the step-by-step implementation:
-
Return a closure function: The
partialfunction returns an anonymous function that accepts...restArgs(using rest parameters to collect all arguments into an array). -
Initialize pointer for restArgs: We start with
i = 0to track our current position in therestArgsarray. -
Replace placeholders in args:
for (let j = 0; j < args.length; ++j) { if (args[j] === '_') { args[j] = restArgs[i++]; } }- Iterate through each element in
argsusing indexj - When we find a placeholder
"_", replace it withrestArgs[i] - Increment
iafter each replacement to move to the next value inrestArgs - Non-placeholder values in
argsremain unchanged
- Iterate through each element in
-
Append remaining arguments:
while (i < restArgs.length) { args.push(restArgs[i++]); }- After replacing all placeholders, check if there are unused values in
restArgs - Append any remaining values to the end of
args - This handles cases where
restArgshas more values than there are placeholders
- After replacing all placeholders, check if there are unused values in
-
Call the original function:
return fn(...args);- Use the spread operator to pass the modified
argsarray as individual arguments - Return the result of calling
fnwith these arguments
- Use the spread operator to pass the modified
Important Note: This implementation modifies the args array in place. In a production environment, you might want to create a copy of args first to avoid side effects if the same partial function is called multiple times:
const newArgs = [...args]; // Create a copy // Then work with newArgs instead of args
The time complexity is O(n + m) where n is the length of args and m is the length of restArgs. The space complexity is O(1) if we modify in place, or O(n) if we create a copy of the args array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the solution works:
function multiply(a, b, c) { return a * b * c; } const partialMultiply = partial(multiply, [2, "_", "_"]); const result = partialMultiply(3, 4, 5); // What happens here?
Step 1: Create the partial function
- We call
partial(multiply, [2, "_", "_"]) - This returns a new function that "remembers"
fn = multiplyandargs = [2, "_", "_"]
Step 2: Call the partial function with restArgs = [3, 4, 5]
Now let's trace through the algorithm:
Initialize pointer: i = 0 (points to first element of restArgs)
Replace placeholders loop:
j = 0:args[0] = 2(not "_"), skipj = 1:args[1] = "_"→ Replace withrestArgs[0] = 3, incrementito 1argsbecomes[2, 3, "_"]
j = 2:args[2] = "_"→ Replace withrestArgs[1] = 4, incrementito 2argsbecomes[2, 3, 4]
Append remaining arguments:
i = 2,restArgs.length = 3, soi < restArgs.lengthis true- Append
restArgs[2] = 5to args argsbecomes[2, 3, 4, 5]
Call original function:
- Execute
multiply(...[2, 3, 4, 5])which ismultiply(2, 3, 4, 5) - Since
multiplyonly uses 3 parameters, it computes2 * 3 * 4 = 24 - The extra argument
5is ignored
Result: Returns 24
This example demonstrates all key aspects:
- Preserving non-placeholder values (the
2) - Replacing placeholders in order (
"_"→3, then"_"→4) - Appending excess arguments (the
5) - Proper function invocation with spread operator
Time and Space Complexity
Time Complexity: O(n) where n is the total number of arguments (args.length + restArgs.length)
- The first for loop iterates through
args.lengthelements:O(args.length) - The while loop iterates through remaining
restArgselements:O(restArgs.length) - The spread operation
fn(...args)takesO(args.length)time to pass arguments - Overall:
O(args.length + restArgs.length)=O(n)
Space Complexity: O(n) where n is the total number of arguments
- The returned function creates a closure that captures the original
argsarray:O(args.length) - The
argsarray is mutated in place, but can grow by pushing remainingrestArgs: worst caseO(args.length + restArgs.length) - The spread operation creates a temporary argument list:
O(args.length) - Overall:
O(args.length + restArgs.length)=O(n)
Note: There's a bug in this implementation - the args array is mutated on each call to the returned function, which means the function will not work correctly if called multiple times. The placeholders '_' will be permanently replaced after the first call.
Common Pitfalls
1. Mutating the Original Args Array
The most critical pitfall is modifying the original args array in place. This causes the partial function to behave incorrectly when called multiple times.
Problem Example:
def add(a, b, c): return a + b + c # BAD: Modifying args directly def partial_buggy(fn, args): def wrapper(*rest_args): rest_index = 0 # Directly modifying args - BUG! for i in range(len(args)): if args[i] == '_': args[i] = rest_args[rest_index] rest_index += 1 # ... rest of code return fn(*args) return wrapper partial_add = partial_buggy(add, ['_', '_', 3]) print(partial_add(1, 2)) # Returns 6 (correct) print(partial_add(4, 5)) # Returns 12 (incorrect! Should also be 12, but args is now [1, 2, 3])
Solution: Always create a copy of the args array:
def wrapper(*rest_args): final_args = args.copy() # Create a copy! # Now work with final_args instead of args
2. Not Handling Insufficient Rest Arguments
When there are more placeholders than provided rest arguments, the code might crash or leave placeholders unreplaced.
Problem Example:
partial_add = partial(add, ['_', '_', '_']) partial_add(1, 2) # Only 2 arguments for 3 placeholders
Solution: Check bounds before accessing rest_args:
for i in range(len(final_args)): if final_args[i] == '_': if rest_args_index < len(rest_args): # Bounds check final_args[i] = rest_args[rest_args_index] rest_args_index += 1 # Optionally: else leave as '_' or replace with None/default value
3. Using Wrong Comparison for Placeholder Check
Using is instead of == for string comparison can fail unexpectedly due to Python's string interning behavior.
Problem Example:
# BAD: Using identity comparison if args[i] is '_': # May not work reliably # ... # GOOD: Using equality comparison if args[i] == '_': # Always works correctly # ...
4. Not Preserving Function Metadata
The wrapper function loses the original function's metadata (name, docstring, etc.).
Solution: Use functools.wraps decorator:
from functools import wraps def partial(fn, args): @wraps(fn) # Preserves fn's metadata def wrapper(*rest_args): # ... implementation return wrapper
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!