What is the in-place alternative to Array.prototype.filter()

Solution 1:

Is there an in-place alternative to filter

No, but it's not hard to write your own. Here is an approach which squeezes out all the values which fail a condition.

function filterInPlace(a, condition) {
  let i = 0, j = 0;

  while (i < a.length) {
    const val = a[i];
    if (condition(val, i, a)) a[j++] = val;
    i++;
  }

  a.length = j;
  return a;
}

condition is designed to have the same signature as the callback passed to Array#filter, namely (value, index, array). For complete compatibility with Array#filter, you could also accept a fourth thisArg parameter.

Using forEach

Using forEach has the minor advantage that it will skip empty slots. This version:

  • Compacts arrays with empty slots
  • Implements thisArg
  • Skipps the assignment, if we have not yet encountered a failing element

function filterInPlace(a, condition, thisArg) {
  let j = 0;

  a.forEach((e, i) => { 
    if (condition.call(thisArg, e, i, a)) {
      if (i!==j) a[j] = e; 
      j++;
    }
  });

  a.length = j;
  return a;
}

a = [ 1,, 3 ];
document.write('<br>[',a,']');

filterInPlace(a, x=>true);
document.write('<br>[',a,'] compaction when nothing changed');

b = [ 1,,3,,5 ];
document.write('<br>[',b,']');

filterInPlace(b, x=>x!==5);
document.write('<br>[',b,'] with 5 removed');

Solution 2:

You could use the following:

array.splice(0, array.length,...array.filter(/*YOUR FUNCTION HERE*/))

Explanation:

  • Splice acts in place
  • First argument means we start at the start of the array
  • Second means we delete the entire array
  • Third means we replace it with its filtered copy
  • The ... is the spread operator (ES6 only) and changes each member of the array into a separate argument

Solution 3:

What you could use

  • Array#filter returns an array with the same elements, but not necesserily all.
  • Array#map returns something for each loop, the result is an array with the same length as the source array.
  • Array#forEach returns nothing, but every element is processed, like above.
  • Array#reduce returns what ever you want.
  • Array#some/Array#every returns a boolean value.

But nothing from above is mutating the original array in question of length in situ.

I suggest to use a while loop, beginning from the last element and apply splice to the element, you want to remove.

This keeps the index valid and allows to decrement for every loop.

Example:

var array = [0, 1, 2, 3, 4, 5],
    i = array.length;

while (i--) {
    if (array[i] % 2) {
        array.splice(i, 1);
    }
}
console.log(array);

Solution 4:

If you are able to add a third-party library, have a look at lodash.remove:

predicate = function(element) {
  return element == "to remove"
}
lodash.remove(array, predicate)