Dividing an array by filter function
JavascriptJavascript 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']]
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[]]
);