2626. Array Reduce Transformation
Problem Description
You need to implement a reduce
function that processes an array of integers using a reducer function and returns a single accumulated value.
The function takes three parameters:
nums
: an integer array to processfn
: a reducer function that takes two numbers (accumulator and current value) and returns a numberinit
: the initial value for the accumulator
The function should work by:
- Starting with the accumulator set to
init
- For each element in the array (in order), calling
fn(accumulator, currentElement)
and updating the accumulator with the result - Returning the final accumulator value after processing all elements
The process follows this pattern:
- First:
val = fn(init, nums[0])
- Then:
val = fn(val, nums[1])
- Then:
val = fn(val, nums[2])
- Continue until all elements are processed
If the array is empty, simply return init
.
The constraint is that you cannot use the built-in Array.reduce
method - you must implement the reduction logic yourself using loops or other constructs.
Intuition
The key insight is that reduction is fundamentally about maintaining a running accumulator that gets updated with each element in the array. Think of it like a snowball rolling down a hill - it starts with an initial size (init
) and grows by incorporating each element it encounters along the way.
Since we can't use the built-in reduce
method, we need to manually replicate what it does internally. The pattern is straightforward: we need to visit each element exactly once, in order, and update our accumulator at each step.
The natural approach is to use a loop that iterates through the array. We need a variable to hold our accumulated value - let's call it acc
. We initialize acc
with the init
value provided. Then, for each element in the array, we apply the reducer function fn
with the current accumulator value and the current element, storing the result back in acc
.
The beauty of this approach is its simplicity - we're essentially maintaining state (acc
) that evolves as we process each element sequentially. After processing all elements, acc
contains our final result. If the array is empty, the loop never executes, and we naturally return the initial value, which handles the edge case perfectly.
This mimics exactly what the built-in reduce
does under the hood - it's just a controlled iteration with state management, where the state transformation is defined by the provided function fn
.
Solution Approach
The implementation follows a straightforward iterative approach:
-
Initialize the accumulator: Create a variable
acc
and set it to theinit
value. This serves as our running total that will be updated throughout the iteration.let acc: number = init;
-
Iterate through the array: Use a
for...of
loop to traverse each element in thenums
array. This ensures we process elements in order from first to last.for (const x of nums) {
-
Apply the reducer function: For each element
x
, call the reducer functionfn
with the current accumulator value and the current element. The result becomes the new accumulator value.acc = fn(acc, x);
-
Return the final result: After the loop completes (all elements have been processed), return the final value of
acc
.return acc;
The complete flow works as follows:
- If
nums = [1, 2, 3]
,fn = (a, b) => a + b
, andinit = 0
- Iteration 1:
acc = fn(0, 1) = 1
- Iteration 2:
acc = fn(1, 2) = 3
- Iteration 3:
acc = fn(3, 3) = 6
- Return:
6
The time complexity is O(n)
where n
is the length of the array, as we visit each element exactly once. The space complexity is O(1)
as we only use a single variable to store the accumulator regardless of the input size.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the reduction process works step by step.
Example: Sum all elements in an array
nums = [2, 5, 3]
fn = (acc, curr) => acc + curr
(adds two numbers)init = 10
Step-by-step execution:
-
Initialize: Start with
acc = 10
(our initial value) -
First iteration (process element
2
):- Call
fn(10, 2)
- Result:
10 + 2 = 12
- Update:
acc = 12
- Call
-
Second iteration (process element
5
):- Call
fn(12, 5)
- Result:
12 + 5 = 17
- Update:
acc = 17
- Call
-
Third iteration (process element
3
):- Call
fn(17, 3)
- Result:
17 + 3 = 20
- Update:
acc = 20
- Call
-
Return: Final value is
20
The accumulator evolved as: 10 → 12 → 17 → 20
Another Example: Find the product of all elements
nums = [2, 3, 4]
fn = (acc, curr) => acc * curr
(multiplies two numbers)init = 1
Execution trace:
- Start:
acc = 1
- After processing
2
:acc = 1 * 2 = 2
- After processing
3
:acc = 2 * 3 = 6
- After processing
4
:acc = 6 * 4 = 24
- Return:
24
Edge Case: Empty array
nums = []
fn = (acc, curr) => acc + curr
init = 100
Since the array is empty, the loop never executes, and we simply return init = 100
.
Solution Implementation
1from typing import Callable, List
2
3def reduce(nums: List[int], fn: Callable[[int, int], int], init: int) -> int:
4 """
5 Reduces a list of numbers to a single value by applying a reducer function
6
7 Args:
8 nums: List of numbers to reduce
9 fn: Reducer function to apply on each element
10 init: Initial value for the accumulator
11
12 Returns:
13 The final accumulated value after processing all elements
14 """
15 # Initialize accumulator with the provided initial value
16 accumulator = init
17
18 # Iterate through each number in the list
19 for current_value in nums:
20 # Apply the reducer function to update the accumulator
21 accumulator = fn(accumulator, current_value)
22
23 # Return the final accumulated result
24 return accumulator
25
1/**
2 * Interface definition for a reducer function that takes an accumulator and current value
3 * and returns a new accumulator value
4 */
5@FunctionalInterface
6interface Fn {
7 int apply(int accum, int curr);
8}
9
10/**
11 * Class containing the reduce utility method
12 */
13class ArrayReducer {
14 /**
15 * Reduces an array of numbers to a single value by applying a reducer function
16 * @param nums - Array of numbers to reduce
17 * @param fn - Reducer function to apply on each element
18 * @param init - Initial value for the accumulator
19 * @return The final accumulated value after processing all elements
20 */
21 public static int reduce(int[] nums, Fn fn, int init) {
22 // Initialize accumulator with the provided initial value
23 int accumulator = init;
24
25 // Iterate through each number in the array
26 for (int currentValue : nums) {
27 // Apply the reducer function to update the accumulator
28 accumulator = fn.apply(accumulator, currentValue);
29 }
30
31 // Return the final accumulated result
32 return accumulator;
33 }
34}
35
1#include <vector>
2#include <functional>
3
4/**
5 * Type definition for a reducer function that takes an accumulator and current value
6 * and returns a new accumulator value
7 */
8using Fn = std::function<int(int, int)>;
9
10/**
11 * Reduces an array of numbers to a single value by applying a reducer function
12 * @param nums - Array of numbers to reduce
13 * @param fn - Reducer function to apply on each element
14 * @param init - Initial value for the accumulator
15 * @return The final accumulated value after processing all elements
16 */
17int reduce(const std::vector<int>& nums, Fn fn, int init) {
18 // Initialize accumulator with the provided initial value
19 int accumulator = init;
20
21 // Iterate through each number in the array
22 for (const int& currentValue : nums) {
23 // Apply the reducer function to update the accumulator
24 accumulator = fn(accumulator, currentValue);
25 }
26
27 // Return the final accumulated result
28 return accumulator;
29}
30
1/**
2 * Type definition for a reducer function that takes an accumulator and current value
3 * and returns a new accumulator value
4 */
5type Fn = (accum: number, curr: number) => number;
6
7/**
8 * Reduces an array of numbers to a single value by applying a reducer function
9 * @param nums - Array of numbers to reduce
10 * @param fn - Reducer function to apply on each element
11 * @param init - Initial value for the accumulator
12 * @returns The final accumulated value after processing all elements
13 */
14function reduce(nums: number[], fn: Fn, init: number): number {
15 // Initialize accumulator with the provided initial value
16 let accumulator: number = init;
17
18 // Iterate through each number in the array
19 for (const currentValue of nums) {
20 // Apply the reducer function to update the accumulator
21 accumulator = fn(accumulator, currentValue);
22 }
23
24 // Return the final accumulated result
25 return accumulator;
26}
27
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the nums
array. The algorithm iterates through each element of the array exactly once, and for each element, it performs a single function call fn(acc, x)
. Assuming the reducer function fn
executes in constant time O(1)
, the overall time complexity is linear.
Space Complexity: O(1)
for the algorithm itself. The function only uses a fixed amount of extra space for the accumulator variable acc
and the loop iterator x
, regardless of the input size. The space used does not grow with the size of the input array. Note that this analysis assumes the reducer function fn
does not use additional space that scales with input size.
Common Pitfalls
1. Mutating the Original Array
A common mistake is accidentally modifying the input array during the reduction process. While the basic implementation shown doesn't have this issue, developers sometimes try to optimize or add features that inadvertently change the original data.
Problematic Example:
def reduce(nums: List[int], fn: Callable[[int, int], int], init: int) -> int:
accumulator = init
# Trying to "optimize" by modifying the array
for i in range(len(nums)):
nums[i] = fn(accumulator, nums[i]) # DON'T DO THIS - mutates input!
accumulator = nums[i]
return accumulator
Solution: Always treat the input array as read-only. Use the current value directly without modifying the array:
def reduce(nums: List[int], fn: Callable[[int, int], int], init: int) -> int:
accumulator = init
for current_value in nums:
accumulator = fn(accumulator, current_value) # Only update accumulator
return accumulator
2. Incorrect Handling of Side Effects in the Reducer Function
If the reducer function has side effects or depends on external state, the reduction might produce unexpected results or fail entirely.
Problematic Example:
counter = 0
def problematic_reducer(acc, val):
global counter
counter += 1
return acc + val * counter # Result depends on external state!
# This will give different results if called multiple times
result1 = reduce([1, 2, 3], problematic_reducer, 0)
result2 = reduce([1, 2, 3], problematic_reducer, 0) # Different result!
Solution: Ensure the reducer function is pure (no side effects, deterministic):
def pure_reducer(acc, val):
return acc + val # Pure function - same inputs always give same output
3. Type Mismatches Between Init and Array Elements
When the initial value type doesn't match what the reducer function expects, it can cause runtime errors or unexpected behavior.
Problematic Example:
# Init is a string, but nums contains integers
def concat_reducer(acc, val):
return str(acc) + str(val)
# This works but might not be what you expect
result = reduce([1, 2, 3], concat_reducer, "") # Returns "123"
# This could fail with certain reducers
def multiply_reducer(acc, val):
return acc * val
result = reduce([1, 2, 3], multiply_reducer, "0") # TypeError or unexpected result
Solution: Ensure type consistency between init
and the expected accumulator type:
# For multiplication, use numeric init
result = reduce([1, 2, 3], multiply_reducer, 1) # Correct
# For string concatenation, convert appropriately
def safe_concat_reducer(acc, val):
return str(acc) + str(val)
result = reduce([1, 2, 3], safe_concat_reducer, "") # Clear intent
4. Not Handling Empty Arrays Properly
While the implementation correctly returns init
for empty arrays, developers sometimes forget to test this edge case or make assumptions about non-empty arrays.
Problematic Assumption:
def reduce_with_first(nums: List[int], fn: Callable[[int, int], int]) -> int:
# Assumes array is non-empty - will crash on empty array!
accumulator = nums[0] # IndexError if nums is empty
for i in range(1, len(nums)):
accumulator = fn(accumulator, nums[i])
return accumulator
Solution: Always handle the empty array case explicitly:
def reduce(nums: List[int], fn: Callable[[int, int], int], init: int) -> int:
if not nums: # Explicit check (though not necessary with for loop)
return init
accumulator = init
for current_value in nums:
accumulator = fn(accumulator, current_value)
return accumulator
How does merge sort divide the problem into subproblems?
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 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
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!