Dividing an array by filter function

Javascript

Javascript Problem Overview


I have a Javascript array that I would like to split into two based on whether a function called on each element returns true or false. Essentially, this is an array.filter, but I'd like to also have on hand the elements that were filtered out.

Currently, my plan is to use array.forEach and call the predicate function on each element. Depending on whether this is true or false, I will push the current element onto one of the two new arrays. Is there a more elegant or otherwise better way to do this? An array.filter where the will push the element onto another array before it returns false, for instance?

Javascript Solutions


Solution 1 - Javascript

With ES6 you can make use of the spread syntax with reduce:

function partition(array, isValid) {
  return array.reduce(([pass, fail], elem) => {
    return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
  }, [[], []]);
}

const [pass, fail] = partition(myArray, (e) => e > 5);

Or on a single line:

const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);

Solution 2 - Javascript

You can use lodash.partition

var users = [
  { 'user': 'barney',  'age': 36, 'active': false },
  { 'user': 'fred',    'age': 40, 'active': true },
  { 'user': 'pebbles', 'age': 1,  'active': false }
];

_.partition(users, function(o) { return o.active; });
// → objects for [['fred'], ['barney', 'pebbles']]

// The `_.matches` iteratee shorthand.
_.partition(users, { 'age': 1, 'active': false });
// → objects for [['pebbles'], ['barney', 'fred']]

// The `_.matchesProperty` iteratee shorthand.
_.partition(users, ['active', false]);
// → objects for [['barney', 'pebbles'], ['fred']]

// The `_.property` iteratee shorthand.
_.partition(users, 'active');
// → objects for [['fred'], ['barney', 'pebbles']]

or ramda.partition

R.partition(R.contains('s'), ['sss', 'ttt', 'foo', 'bars']);
// => [ [ 'sss', 'bars' ],  [ 'ttt', 'foo' ] ]

R.partition(R.contains('s'), { a: 'sss', b: 'ttt', foo: 'bars' });
// => [ { a: 'sss', foo: 'bars' }, { b: 'ttt' }  ]

Solution 3 - Javascript

I came up with this little guy. It uses for each and all that like you described, but it looks clean and succinct in my opinion.

//Partition function
function partition(array, filter) {
  let pass = [], fail = [];
  array.forEach((e, idx, arr) => (filter(e, idx, arr) ? pass : fail).push(e));
  return [pass, fail];
}

//Run it with some dummy data and filter
const [lessThan5, greaterThanEqual5] = partition([0,1,4,3,5,7,9,2,4,6,8,9,0,1,2,4,6], e => e < 5);

//Output
console.log(lessThan5);
console.log(greaterThanEqual5);

Solution 4 - Javascript

You can use reduce for it:

function partition(array, callback){
  return array.reduce(function(result, element, i) {
    callback(element, i, array) 
      ? result[0].push(element) 
      : result[1].push(element);
    
        return result;
      }, [[],[]]
    );
 };

Update. Using ES6 syntax you also can do that using recursion (updated to avoid creating new arrays on every iteration):

function partition([current, ...tail], f, left = [], right = []) {
    if(current === undefined) {
        return [left, right];
    }
    if(f(current)) {
        left.push(current);
        return partition(tail, f, left, right);
    }
    right.push(current);
    return partition(tail, f, left, right);
}

Solution 5 - Javascript

This sounds very similar to Ruby's Enumerable#partition method.

If the function can't have side-effects (i.e., it can't alter the original array), then there's no more efficient way to partition the array than iterating over each element and pushing the element to one of your two arrays.

That being said, it's arguably more "elegant" to create a method on Array to perform this function. In this example, the filter function is executed in the context of the original array (i.e., this will be the original array), and it receives the element and the index of the element as arguments (similar to jQuery's each method):

Array.prototype.partition = function (f){
  var matched = [],
      unmatched = [],
      i = 0,
      j = this.length;
  
  for (; i < j; i++){
    (f.call(this, this[i], i) ? matched : unmatched).push(this[i]);
  }
  
  return [matched, unmatched];
};

console.log([1, 2, 3, 4, 5].partition(function (n, i){
  return n % 2 == 0;
}));

//=> [ [ 2, 4 ], [ 1, 3, 5 ] ]

Solution 6 - Javascript

In filter function you can push your false items into another variable outside function:

var bad = [], good = [1,2,3,4,5];
good = good.filter(function (value) { if (value === false) { bad.push(value) } else { return true});

Of course value === false need to be real comparasion ;)

But it do almost that same operation like forEach. I think you should use forEach for better code readability.

Solution 7 - Javascript

What about this?

[1,4,3,5,3,2].reduce( (s, x) => { s[ x > 3 ].push(x); return s;} , {true: [], false:[]} )

Probably this is more efficient than the spread operator

Or a bit shorter, but uglier

[1,4,3,5,3,2].reduce( (s, x) => s[ x > 3 ].push(x)?s:s , {true: [], false:[]} )

Solution 8 - Javascript

A lot of answers here use Array.prototype.reduce to build a mutable accumulator, and rightfully point out that for large arrays, this is more efficient than, say, using a spread operator to copy a new array each iteration. The downside is that it's not as pretty as a "pure" expression using the short lambda syntax.

But a way around that is to use the comma operator. In C-like languages, comma is an operator that always returns the right hand operand. You can use this to create an expression that calls a void function and returns a value.

function partition(array, predicate) {
    return array.reduce((acc, item) => predicate(item)
        ? (acc[0].push(item), acc)
        : (acc[1].push(item), acc), [[], []]);
}

If you take advantage of the fact that a boolean expression implicitly casts to a number as 0 and 1, and you can make it even more concise, although I don't think it's as readable:

function partition(array, predicate) {
    return array.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);
}

Usage:

const [trues, falses] = partition(['aardvark', 'cat', 'apple'], i => i.startsWith('a'));
console.log(trues); // ['aardvark', 'apple']
console.log(falses); // ['cat']

Solution 9 - Javascript

Easy to read one.

const partition = (arr, condition) => {
    const trues = arr.filter(el => condition(el));
    const falses = arr.filter(el => !condition(el));
    return [trues, falses];
};

// sample usage
const nums = [1,2,3,4,5,6,7]
const [evens, odds] = partition(nums, (el) => el%2 == 0)

Solution 10 - Javascript

Try this:

function filter(a, fun) {
    var ret = { good: [], bad: [] };
    for (var i = 0; i < a.length; i++)
        if (fun(a[i])
            ret.good.push(a[i]);
        else
            ret.bad.push(a[i]);
    return ret;
}

DEMO

Solution 11 - Javascript

I ended up doing this because it's easy to understand:

const partition = (array, isValid) => {
  const pass = []
  const fail = []
  array.forEach(element => {
    if (isValid(element)) {
      pass.push(element)
    } else {
      fail.push(element)
    }
  })
  return [pass, fail]
}

// usage
const [pass, fail] = partition([1, 2, 3, 4, 5], (element) => element > 3)

And the same method including types for typescript:

const partition = <T>(array: T[], isValid: (element: T) => boolean): [T[], T[]] => {
  const pass: T[] = []
  const fail: T[] = []
  array.forEach(element => {
    if (isValid(element)) {
      pass.push(element)
    } else {
      fail.push(element)
    }
  })
  return [pass, fail]
}

// usage
const [pass, fail] = partition([1, 2, 3, 4, 5], (element: number) => element > 3)

Solution 12 - Javascript

I know there are multiple solutions already but I took the liberty of putting together the best bits of the answers above and used extension methods on Typescript. Copy and paste and it just works:

declare global {

  interface Array<T> {
    partition(this: T[], predicate: (e: T) => boolean): T[][];
  }

}

if(!Array.prototype.partition){

  Array.prototype.partition = function<T>(this: T[], predicate: (e: T) => boolean): T[][] {

    return this.reduce<T[][]>(([pass, fail], elem) => {
      (predicate(elem) ? pass : fail).push(elem);
      return [pass, fail];
    }, [[], []]);

  }
}

Usage:


const numbers = [1, 2, 3, 4, 5, 6];
const [even, odd] = numbers.partition(n => n % 2 === 0);

Solution 13 - Javascript

Lodash partition alternative, same as the first solution of @Yaremenko Andrii but shorter syntax

function partition(arr, callback) {
  return arr.reduce(
    (acc, val, i, arr) => {
      acc[callback(val, i, arr) ? 0 : 1].push(val)
      return acc
    },
    [[], []]
  )
}

Solution 14 - Javascript

ONE-LINER Partition

const partitionBy = (arr, predicate) =>
	arr.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);

DEMO

// to make it consistent to filter pass index and array as arguments
const partitionBy = (arr, predicate) =>
    arr.reduce(
        (acc, item, index, array) => (
            acc[+!predicate(item, index, array)].push(item), acc
        ),
        [[], []]
    );

console.log(partitionBy([1, 2, 3, 4, 5], x => x % 2 === 0));
console.log(partitionBy([..."ABCD"], (x, i) => i % 2 === 0));

For Typescript (v4.5)

const partitionBy = <T>(
  arr: T[],
  predicate: (v: T, i: number, ar: T[]) => boolean
) =>
  arr.reduce(
    (acc, item, index, array) => {
      acc[+!predicate(item, index, array)].push(item);
      return acc;
    },
    [[], []] as [T[], T[]]
  );

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMike ChenView Question on Stackoverflow
Solution 1 - JavascriptbrazaView Answer on Stackoverflow
Solution 2 - JavascriptZoltan KochanView Answer on Stackoverflow
Solution 3 - JavascriptUDrakeView Answer on Stackoverflow
Solution 4 - JavascriptYaremenko AndriiView Answer on Stackoverflow
Solution 5 - JavascriptBrandanView Answer on Stackoverflow
Solution 6 - JavascriptSetthaseView Answer on Stackoverflow
Solution 7 - JavascriptVerebView Answer on Stackoverflow
Solution 8 - JavascriptparktomatomiView Answer on Stackoverflow
Solution 9 - JavascriptkazuwombatView Answer on Stackoverflow
Solution 10 - JavascriptqwertymkView Answer on Stackoverflow
Solution 11 - JavascriptAndreas GassmannView Answer on Stackoverflow
Solution 12 - JavascriptSebastianView Answer on Stackoverflow
Solution 13 - Javascriptnos nartView Answer on Stackoverflow
Solution 14 - JavascriptnkitkuView Answer on Stackoverflow