How to get the excluded elements when using javascript's filter

Viewed 131

Javascript's filter returns the array with all the elements passing the test.

How how can you easily get all the elements that failed the test without running the test again, but for the converse? How is the best way to do it, even if you have to run the test again.

let arr; // this is the array on which the filter will be run [SET ELSEWHERE]
let fn; // The filter function [SET ELSEWHERE]
let goodElements; // This will be the new array of the good elements passing the test
let badElements; // This will be the new array of the elements failing the test

goodElements = arr.filter(fn);

// SO HOW IS badElements set????

How is badElements set?

6 Answers

Don't use filter(). If you want to partition the data into two arrays, do it yourself.

function partition(array, fn) {
  let goodArray = [],
    badArray = [];
  array.forEach(el => {
    if (fn(el)) {
      goodArray.push(el);
    } else {
      badArray.push(el);
    }
  });
  return [goodArray, badArray];
}

let [goodElemements, badElements] = partition(arr, fn);

You could also use reduce()

function partition(array, fn) {
  return array.reduce(acc, el => {
    if (fn(el)) {
      acc[0].push(el);
    } else {
      acc[1].push(el);
    }
  }, [[],[]]);
}

let [goodElemements, badElements] = partition(arr, fn);

If you don't want to do two iterations, you can use a for loop and a ternary operator:

let arr = [1, 2, 3];
let fn = (e) => e % 2 == 0;
let goodElements = [];
let badElements = [];

for(const e of arr) (fn(e) ? goodElements : badElements).push(e);
console.log(goodElements);
console.log(badElements);

Otherwise, just invert the condition with the ! operator:

let arr = [1, 2, 3];
let fn = (e) => e % 2 == 0;
let goodElements;
let badElements;

goodElements = arr.filter(fn);
badElements = arr.filter(e => !fn(e));
console.log(goodElements);
console.log(badElements);

If you want to do this strictly with Array.filter and only one loop, then consider something like this:

UPD: based on @AlvaroFlaƱoLarrondo comment, added external condition function to current approach.

// Array of elements
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];

// External filter method
const fn = e => e.length > 6;

// Define empty bad array
const bad = [];

// Define good array as result from filter function
const good = words.filter(word => {
  // In filter condition return good values
  if(fn(word)) return word;
  // And skip else values by just pushing them
  // to bad array, without returning
  else bad.push(word);
});

// Results
console.log(good);
console.log(bad);

I see two possible routes:

// in this case, if it's not in `goodElements`, it's a bad 'un
badElements = arr.filter( el => !goodElements.includes(el) );

or this:

// we don't **know** if fn needs the optional parameters, so we will
//  simply pass them. If it doesn't need 'em, they'll be ignored.
badElements = arr.filter( (el, idx, arr) => !fn(el, idx, arr) );

You could use reduce in order to split the array into two arrays based on a predicate, like in this example:

const arr = [1, 0, true, false, "", "foo"];
const fn = element => !element;

const [goodElements, badElements] = arr.reduce(
  ([truthies, falsies], cur) =>
    fn(cur) ? [truthies, [...falsies, cur]] : [[...truthies, cur], falsies],
  [[], []]
);

console.log(goodElements, badElements);

const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const fn = (x) => x % 2 === 0

const removeItems = (array, itemsToRemove) => {
    return array.filter(v => {
      return !itemsToRemove.includes(v);
    });
}
  
const goodElements = arr.filter(fn)
console.log(goodElements) // [ 0, 2, 4, 6, 8 ]

const badElements = removeItems(arr, goodElements)
console.log(badElements) // [ 1, 3, 5, 7, 9 ]
Related