Facebook Pixel

2634. Filter Elements from Array

Problem Description

You are given an integer array arr and a filtering function fn. Your task is to create and return a new filtered array filteredArr.

The filtering function fn accepts two parameters:

  • arr[i] - the current number from the array
  • i - the index of the current element

To build filteredArr, you need to iterate through the original array and include only those elements where fn(arr[i], i) returns a truthy value. A truthy value is any value that evaluates to true when converted to a boolean using Boolean(value).

The implementation must be done manually without using the built-in Array.filter method.

For example, if you have an array [1, 2, 3] and a function that returns true for even numbers, the filtered array would be [2]. Or if the function returns true for elements at odd indices, the filtered array would be [2] (since 2 is at index 1).

The solution iterates through the array using a for loop, checks each element with the provided function, and pushes elements that pass the test into a new array. This approach has a time complexity of O(n) where n is the length of the input array, as it visits each element exactly once.

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

Intuition

The problem asks us to recreate the filtering functionality without using the built-in filter method. When we think about what filtering actually does, it's essentially examining each element in a collection and deciding whether to keep it or discard it based on some condition.

The natural approach is to mimic this process manually. We need to:

  1. Look at each element one by one
  2. Apply the given test function to determine if we should keep it
  3. Collect the "keeper" elements somewhere

This leads us directly to the idea of creating an empty result array and iterating through the original array. For each element, we call the provided function fn with the element value and its index. If the function returns a truthy value, we know this element should be included in our filtered result.

The key insight is that filtering is fundamentally a selective copying operation - we're creating a new array that contains a subset of the original elements. We can't modify the original array in place because we need to return a new filtered array. Therefore, we need a temporary storage (the ans array) to collect our filtered elements as we go through the original array.

Using a simple for loop gives us access to both the element value arr[i] and its index i, which are exactly what the filtering function needs as arguments. When fn(arr[i], i) returns a truthy value, we add that element to our result using push(). This preserves the relative order of elements from the original array, which is an important characteristic of filtering operations.

Solution Approach

The solution uses a straightforward traversal approach to implement the filtering logic manually.

We start by initializing an empty array ans that will store our filtered results:

const ans: number[] = [];

Next, we iterate through the input array using a standard for loop from index 0 to arr.length - 1:

for (let i = 0; i < arr.length; ++i) {
    // processing logic here
}

For each element at position i, we evaluate the filtering condition by calling the provided function fn with two arguments:

  • arr[i]: the current element value
  • i: the current index

The critical part is the conditional check:

if (fn(arr[i], i)) {
    ans.push(arr[i]);
}

If fn(arr[i], i) returns a truthy value, we add the current element to our result array using the push() method. This ensures that only elements satisfying the filter condition are included in the final array.

Finally, we return the ans array containing all filtered elements:

return ans;

The algorithm maintains the original order of elements - if element A appears before element B in the original array, and both pass the filter, then A will appear before B in the filtered array as well. This property is preserved because we traverse the array sequentially and append elements in the order they're encountered.

The time complexity is O(n) where n is the length of the input array, as we visit each element exactly once. The space complexity is O(k) where k is the number of elements that pass the filter condition, which in the worst case could be O(n) if all elements pass the filter.

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 concrete example to see how the solution works step by step.

Given:

  • Array: arr = [5, 12, 8, 3, 10]
  • Filter function: fn = (val, index) => val > 7 (keeps elements greater than 7)

Step-by-step execution:

  1. Initialize result array:

    • ans = []
  2. Iteration 1 (i = 0):

    • Current element: arr[0] = 5
    • Evaluate: fn(5, 0)5 > 7false
    • Action: Don't add to ans
    • Result: ans = []
  3. Iteration 2 (i = 1):

    • Current element: arr[1] = 12
    • Evaluate: fn(12, 1)12 > 7true
    • Action: Push 12 to ans
    • Result: ans = [12]
  4. Iteration 3 (i = 2):

    • Current element: arr[2] = 8
    • Evaluate: fn(8, 2)8 > 7true
    • Action: Push 8 to ans
    • Result: ans = [12, 8]
  5. Iteration 4 (i = 3):

    • Current element: arr[3] = 3
    • Evaluate: fn(3, 3)3 > 7false
    • Action: Don't add to ans
    • Result: ans = [12, 8]
  6. Iteration 5 (i = 4):

    • Current element: arr[4] = 10
    • Evaluate: fn(10, 4)10 > 7true
    • Action: Push 10 to ans
    • Result: ans = [12, 8, 10]

Final Output: [12, 8, 10]

The filtered array contains only the elements greater than 7, maintaining their original relative order from the input array.

Solution Implementation

1def filter(arr, fn):
2    """
3    Filters an array based on a provided predicate function
4  
5    Args:
6        arr: The input array of numbers to be filtered
7        fn: The predicate function that takes a number and its index, returns truthy/falsy value
8  
9    Returns:
10        A new list containing only elements that satisfy the predicate
11    """
12    # Initialize result list to store filtered elements
13    filtered_result = []
14  
15    # Iterate through each element in the input array with its index
16    for index in range(len(arr)):
17        # Check if current element satisfies the predicate condition
18        if fn(arr[index], index):
19            # Add element to result if predicate returns truthy value
20            filtered_result.append(arr[index])
21  
22    # Return the filtered list
23    return filtered_result
24
1/**
2 * Filters an array based on a provided predicate function
3 * @param arr - The input array of numbers to be filtered
4 * @param fn - The predicate function that takes a number and its index, returns boolean value
5 * @return A new array containing only elements that satisfy the predicate
6 */
7public static int[] filter(int[] arr, BiPredicate<Integer, Integer> fn) {
8    // Initialize a list to store filtered elements (since array size is unknown)
9    List<Integer> filteredResult = new ArrayList<>();
10  
11    // Iterate through each element in the input array
12    for (int index = 0; index < arr.length; index++) {
13        // Check if current element satisfies the predicate condition
14        if (fn.test(arr[index], index)) {
15            // Add element to result if predicate returns true
16            filteredResult.add(arr[index]);
17        }
18    }
19  
20    // Convert the list to an array and return
21    return filteredResult.stream().mapToInt(Integer::intValue).toArray();
22}
23
1#include <vector>
2#include <functional>
3
4/**
5 * Filters an array based on a provided predicate function
6 * @param arr - The input array of numbers to be filtered
7 * @param fn - The predicate function that takes a number and its index, returns boolean value
8 * @returns A new array containing only elements that satisfy the predicate
9 */
10std::vector<int> filter(const std::vector<int>& arr, std::function<bool(int, int)> fn) {
11    // Initialize result vector to store filtered elements
12    std::vector<int> filteredResult;
13  
14    // Iterate through each element in the input array
15    for (int index = 0; index < arr.size(); index++) {
16        // Check if current element satisfies the predicate condition
17        if (fn(arr[index], index)) {
18            // Add element to result if predicate returns true
19            filteredResult.push_back(arr[index]);
20        }
21    }
22  
23    // Return the filtered array
24    return filteredResult;
25}
26
1/**
2 * Filters an array based on a provided predicate function
3 * @param arr - The input array of numbers to be filtered
4 * @param fn - The predicate function that takes a number and its index, returns truthy/falsy value
5 * @returns A new array containing only elements that satisfy the predicate
6 */
7function filter(arr: number[], fn: (n: number, i: number) => any): number[] {
8    // Initialize result array to store filtered elements
9    const filteredResult: number[] = [];
10  
11    // Iterate through each element in the input array
12    for (let index = 0; index < arr.length; index++) {
13        // Check if current element satisfies the predicate condition
14        if (fn(arr[index], index)) {
15            // Add element to result if predicate returns truthy value
16            filteredResult.push(arr[index]);
17        }
18    }
19  
20    // Return the filtered array
21    return filteredResult;
22}
23

Time and Space Complexity

Time Complexity: O(n), where n is the length of the array arr. The algorithm iterates through each element of the input array exactly once using a single for loop. Even though the callback function fn is called for each element, assuming the callback function has O(1) complexity, the overall time complexity remains linear.

Space Complexity: O(1) (excluding the output array). The algorithm only uses a constant amount of extra space for the loop variable i. If we include the output array ans in the space complexity analysis, it would be O(k) where k is the number of elements that satisfy the filter condition, which in the worst case could be O(n) when all elements pass the filter.

Common Pitfalls

1. Misunderstanding Truthy vs. True Values

A frequent mistake is assuming the filtering function must return exactly True or False. In reality, the function can return any value, and Python will evaluate its truthiness.

Problematic assumption:

def filter(arr, fn):
    filtered_result = []
    for index in range(len(arr)):
        # Wrong: checking for exact True value
        if fn(arr[index], index) == True:
            filtered_result.append(arr[index])
    return filtered_result

Why it's wrong: If fn returns truthy values like 1, "yes", or [1], the condition == True will fail even though these should pass the filter.

Correct approach:

def filter(arr, fn):
    filtered_result = []
    for index in range(len(arr)):
        # Correct: relies on Python's truthiness evaluation
        if fn(arr[index], index):
            filtered_result.append(arr[index])
    return filtered_result

2. Modifying the Original Array

Some developers might accidentally modify the input array instead of creating a new one.

Problematic approach:

def filter(arr, fn):
    # Wrong: modifying arr in-place
    i = 0
    while i < len(arr):
        if not fn(arr[i], i):
            arr.pop(i)
        else:
            i += 1
    return arr

Why it's wrong: This modifies the original array, which violates the requirement to return a new filtered array. Additionally, removing elements while iterating causes index shifting issues.

Correct approach: Always create and return a new list without modifying the original.

3. Index Confusion When Function Depends on Original Indices

When the filtering function depends on the index parameter, developers might mistakenly use the filtered array's index instead of the original array's index.

Example scenario:

# Filter function that keeps elements at even indices
fn = lambda val, idx: idx % 2 == 0

arr = [10, 20, 30, 40, 50]
# Expected: [10, 30, 50] (indices 0, 2, 4 from original)

Wrong mental model: Thinking the index parameter should reflect the position in the filtered result rather than the original array position.

4. Not Handling Edge Cases

Failing to consider edge cases like empty arrays or None values.

Incomplete implementation:

def filter(arr, fn):
    # Might fail if arr is None or has unexpected types
    filtered_result = []
    for index in range(len(arr)):
        if fn(arr[index], index):
            filtered_result.append(arr[index])
    return filtered_result

More robust approach:

def filter(arr, fn):
    # Handle None or empty input
    if not arr:
        return []
  
    filtered_result = []
    for index in range(len(arr)):
        try:
            if fn(arr[index], index):
                filtered_result.append(arr[index])
        except Exception:
            # Skip elements that cause errors in the filter function
            continue
  
    return filtered_result
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

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

Load More