2804. Array Prototype ForEach


Problem Description

The problem requires the creation of a custom forEach method that can be applied to all arrays. In JavaScript, the forEach method is a built-in function that executes a provided function once for each array element. However, this custom method should be written without using the native .forEach or other built-in array methods.

The custom forEach method needs to accept two parameters:

  • callback: a function that you want to execute for each element in the array.
  • context: an object that can be referred to as this within the scope of the callback function.

The callback function itself should have access to three arguments:

  1. currentValue: the value of the array element in the current iteration.
  2. index: the index of the current array element.
  3. array: the entire array that the forEach is being executed upon.

The essence of this problem is to enhance the array prototype inside JavaScript, ensuring that this functionality is available on all arrays within that context.

Intuition

The intuition is to replicate the behavior of the built-in forEach functionality manually. We want to achieve three key tasks:

  1. Iterate through each element of the array – we can do this via a for loop that starts at the beginning of the array (index 0) and continues to the end (length of the array).
  2. During each iteration, execute the callback function with the proper arguments – we can call the callback function with currentValue, index, and array.
  3. Respect the context in which callback is executed if provided – we can use the call method on the callback function to set the this value explicitly to the context passed as a parameter.

The solution doesn't return anything because the purpose of forEach is to execute side effects rather than compute and return a value. The forEach method is more about doing something with each array element, like modifying the array or using the elements for some other side effectful operations. This implementation fits the requirement which is to execute the provided function for each of the array's elements with the given context.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

A heap is a ...?

Solution Approach

The implementation of the custom forEach method makes use of JavaScript's prototypal inheritance. By appending a new function to Array.prototype, we ensure that all arrays inherit this method.

Here is the step-by-step breakdown of the approach:

  1. We start by adding a new function to Array.prototype named forEach. This means that every array created in this JavaScript environment will now have access to this custom forEach method.

  2. Our forEach function takes in two parameters: callback and context. The callback is a function that we want to call with each element of the array. The context is optional and is used to specify the value of this inside the callback function when it is called.

  3. We make use of a simple for loop to iterate over the array. The initial index is set to 0, and we loop through until we reach the end of the array, denoted by this.length, because within this function, this refers to the array upon which the forEach method was called.

  4. For each iteration, we use the call method of the callback function to execute it. The call method is a built-in JavaScript method that allows us to call a function with an explicitly set this value – in this case, the context parameter. If context is undefined, the value of this inside the callback will default to the global object, which is the default behavior in a browser, or to undefined in strict mode.

  5. The callback function is called with three arguments: the current element value (this[i]), the current index (i), and the array itself (this). This matches the standard forEach method's signature.

The code does not include any complex algorithms, data structures, or patterns—it is a straightforward iteration using a for loop and function calls, sticking closely to the requirements of how the built-in forEach is expected to function.

The beauty of this approach is in its simplicity and direct manipulation of the Array.prototype to achieve the desired effect across all arrays within the scope of execution.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

Which of the following shows the order of node visit in a Breadth-first Search?

Example Walkthrough

Let's consider an example where we have an array of names and we want to print each name to the console with a greeting. The array is ['Alice', 'Bob', 'Charlie'].

Here's a breakdown of how we could use our custom forEach method to accomplish this:

  1. First, we'd set up our custom forEach method by extending the Array.prototype as described in the solution approach. This method would then be available on every array.

  2. We would define a callback function that takes currentValue (the name in this context), index, and array as arguments. Our callback function would simply print the greeting to the console using the current name:

    1function greeting(name, index) {
    2  console.log(`Hello, ${name}! You are at position ${index + 1}.`);
    3}
  3. We would then call our custom forEach method on our array of names, passing the greeting function as the callback.

    1const names = ['Alice', 'Bob', 'Charlie'];
    2names.forEach(greeting);
  4. When we execute this code, our custom forEach method iterates over the array. For each element:

    a. It calls the greeting function using call, which applies the given context (if any). In this case, we haven't provided a context, so it defaults to the global context.

    b. This results in our greeting function being called with each name and its corresponding index. The greeting function then logs the greeting to the console.

  5. As the loop progresses, greeting would be called with 'Alice' at index 0, 'Bob' at index 1, and 'Charlie' at index 2.

  6. The final output to the console would be:

    1Hello, Alice! You are at position 1.
    2Hello, Bob! You are at position 2.
    3Hello, Charlie! You are at position 3.

This small example illustrates how the custom forEach method behaves similarly to the built-in forEach, allowing us to execute a function for each element in an array without relying on the native method.

Solution Implementation

1class Array(list):
2    # The custom forEach function added to the Array class (inherits from list)
3    def forEach(self, callback, context=None):
4        # Iterate over each element in the list
5        for index, value in enumerate(self):
6            # If context is provided, apply it to the callback
7            if context:
8                callback(value, index, self, context)
9            else:
10                # Call the callback with the current element, its index, and the list itself
11                callback(value, index, self)
12
13# Example usage of the custom forEach function
14
15# Create an instance of Array with numbers
16arr = Array([1, 2, 3])
17
18# Define a callback function that doubles the value of each list element
19def double_values_callback(value, index, array, context=None):
20    # Update the list element to its doubled value
21    array[index] = value * 2
22
23# Define an optional context object (unused in this example)
24context = {"context": True}
25
26# Apply the custom forEach function to the arr Array using the doubleValuesCallback and context
27arr.forEach(double_values_callback, context)
28
29# Print the updated list to the console, which should show doubled values
30print(arr)  # Output will be: [2, 4, 6]
31
1import java.util.function.BiConsumer;
2
3// Custom interface extending the functionality of BiConsumer interface
4interface ForEach<T> extends BiConsumer<T, Integer> {
5}
6
7/**
8 * A utility class to work with arrays
9 */
10class ArrayUtils {
11
12    /**
13     * A custom implementation of the forEach function that operates on arrays.
14     *
15     * @param array     The array on which the operation is performed.
16     * @param callback  The callback function to execute for each element.
17     * @param <T>       The type of the elements in the array.
18     */
19    public static <T> void forEach(T[] array, ForEach<T> callback) {
20        // Iterate over each element in the array
21        for (int i = 0; i < array.length; i++) {
22            // Execute the callback function with the current element and its index
23            callback.accept(array[i], i);
24        }
25    }
26}
27
28/**
29 * Example usage of the custom forEach function.
30 */
31public class Main {
32    public static void main(String[] args) {
33        // Create an array of numbers
34        Integer[] arr = {1, 2, 3};
35      
36        // Define a callback function that doubles the value of each array element
37        ForEach<Integer> doubleValuesCallback = new ForEach<>() {
38            public void accept(Integer value, Integer index) {
39                arr[index] = value * 2;
40            }
41        };
42
43        // Apply the custom forEach function to the arr array using the doubleValuesCallback
44        ArrayUtils.forEach(arr, doubleValuesCallback);
45
46        // Print the updated array to the console, which should show doubled values
47        for (Integer value : arr) {
48            System.out.println(value); // Output will be: 2, 4, 6
49        }
50    }
51}
52
1#include <iostream>
2#include <vector>
3#include <functional>
4
5// Declare a template for a custom forEach function
6template <typename T>
7void forEach(std::vector<T>& vec, std::function<void(T&, size_t, std::vector<T>&)> callback, void* context = nullptr) {
8    // Iterate over each element in the vector
9    for (size_t i = 0; i < vec.size(); ++i){
10        // Call the callback function, passing the current element by reference, its index, and the vector itself
11        callback(vec[i], i, vec);
12    }
13}
14
15int main() {
16    // Example usage of the custom forEach function
17
18    // Create a vector of numbers
19    std::vector<int> arr = {1, 2, 3};
20
21    // Define a callback function that doubles the value of each element of the vector
22    auto doubleValuesCallback = [](int& value, size_t index, std::vector<int>& array){
23        // Double the passed element's value
24        value *= 2;
25    };
26
27    // Define an optional context object (not utilized in this example)
28    // In this C++ version, it's not used but provided for compatibility with the original interface
29    void* context = nullptr;
30
31    // Apply the custom forEach function to the arr vector using the doubleValuesCallback
32    forEach(arr, doubleValuesCallback, context);
33
34    // Print the updated vector to the console, which should show doubled values
35    for (const int& value : arr) {
36        std::cout << value << " "; // Output will be: 2 4 6
37    }
38
39    return 0;
40}
41
1// Extending the Array prototype interface to include the custom forEach function
2interface Array<T> {
3  forEach(callback: (value: T, index: number, array: T[]) => void, context?: any): void;
4}
5
6// Custom implementation of the forEach function that adheres to TypeScript's syntax and type safety
7Array.prototype.forEach = function<T>(this: T[], callback: (value: T, index: number, array: T[]) => void, context?: any): void {
8  // Iterate over each element in the array
9  for (let i = 0; i < this.length; i++) {
10    // Call the callback function with the specified context, passing the current element, its index, and the array itself
11    callback.call(context, this[i], i, this);
12  }
13};
14
15// Example usage of the custom forEach function
16
17// Create an array of numbers
18const arr: number[] = [1, 2, 3];
19
20// Define a callback function that doubles the value of each array element
21const doubleValuesCallback = (value: number, index: number, array: number[]): void => {
22  array[index] = value * 2;
23};
24
25// Define an optional context object (not utilized in this example)
26const context = { "context": true };
27
28// Apply the custom forEach function to the arr array using the doubleValuesCallback and context
29arr.forEach(doubleValuesCallback, context);
30
31// Print the updated array to the console, which should show doubled values
32console.log(arr); // Output will be: [2, 4, 6]
33
Not Sure What to Study? Take the 2-min Quiz:

What data structure does Breadth-first search typically uses to store intermediate states?

Time and Space Complexity

The time complexity of the provided custom forEach function is O(n), where n is the number of elements in the array upon which forEach is called. This is due to the for loop iterating through each element of the array exactly once.

The space complexity of forEach is O(1) (constant space complexity), assuming that the callback function's space complexity is also constant. The space used by the forEach method itself does not grow with the size of the input array because it uses a fixed amount of additional space (a single loop counter i).

Fast Track Your Learning with Our Quick Skills Quiz:

What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫