Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript: place elements that dont match filter predicate into seperate array

This may be a lot simpler than I think, but I've been trying out the .map() and .filter() functions in javascript. What i want to do is create one array using .filter(), and another array for elements that did not match the predicate for the first filter. What I have thus far:

function test(array, predicate){
    var filterTrue = array.filter(predicate);
    var filterFalse = ??
    // rest of method
}

Is there a way to dump the items that do not match the predicate into filterFalse? Probably goes without saying, but the predicate will typically be a function of some kind

EDIT: As an aside, I have tried:

var filterFalse = array.filter(!predicate);

But this doesn't seem to work for reasons I'm still trying to understand(Any help on that would also be greatly appreciated)

like image 907
jbailie1991 Avatar asked Mar 19 '15 17:03

jbailie1991


2 Answers

EDIT:

Below is an implementation of the partition method from lodash in plain JavaScript with TypeScript typings in JSDoc. It uses Array.prototype.reduce. As the JSDoc comment says this partition function does the following:

Returns an array with two arrays at index 0 and 1. The array at index 0 is all the items in arr that passed the predicate truth test by returning a truthy value. The array at index 1 is all the items in arr that failed the predicate truth test by returning a falsy value.

// ----- partition function declaration -----
/** Returns an array with two arrays at index
 * 0 and 1. The array at index 0 is all the items
 * in `arr` that passed the `predicate` truth test by
 * returning a truthy value. The array at index 1 is all the items
 * in `arr` that failed the `predicate` truth test by returning
 * a falsy value.
 * @template {any} T
 * @param {Array<T>} arr
 * @param {(el:T, index:number, arr:Array<T>) => any} predicate
 * @returns {[Array<T>, Array<T>]}
 */
function partition(arr, predicate) {
  return arr.reduce(
    // this callback will be called for each element of arr
    function(partitionsAccumulator, arrElement, i, arr) {
      if (predicate(arrElement, i, arr)) {
        // predicate passed push to left array
        partitionsAccumulator[0].push(arrElement);
      } else {
        // predicate failed push to right array
        partitionsAccumulator[1].push(arrElement);
      }
      // whatever is returned from reduce will become the new value of the
      // first parameter of the reduce callback in this case 
      // partitionsAccumulator variable if there are no more elements
      // this return value will be the return value of the full reduce
      // function.
      return partitionsAccumulator;
    },
    // the initial value of partitionsAccumulator in the callback function above
    // if the arr is empty this will be the return value of the reduce
    [[], []]
  );
}


// ----- function usage examples -----
// This partition gets all numbers which are even in the
// first array (all these numbers returned true for the predicate)
// and returns all numbers which are odd in the second array
var res = partition([1, 2, 3], function(number) {
  return number % 2 === 0;
});
console.log(res); // → [[2], [1, 3]]
// This partition gets all indexes that are more than half
// way through the array.
res = partition([1, 2, 3, 4], function(number, index, array) {
  return index > Math.floor(array.length / 2) - 1;
});
console.log(res); // → [[3, 4], [1, 2]]
// This partition gets all strings with length greater than 4
res = partition(["bam!", "kazaam!", "blam!", "wam!", "jam!"], (string) => {
  return string.length > 4;
});
console.log(res); // → [["kazaam!", "blam!"], ["bam!", "wam!", "jam!"]]

The benefit of using JSDoc types is that if you have an editor like VSCode it will show you the typings and description when you press command on mac or ctrl on windows. That looks like this:

partition function VSCode typings output

VSCode is smart enough to figure out, given that I used a template T in my JSDoc annotation, that T is a number since the array in parameter 1 in this picture is full of numbers. If you pass in an array of strings it will correctly hint the types of the predicate's el parameter and the return item values of the inner arrays as strings, since they too use the template T.


Original Answer

Hate to bring libraries up if you aren't already using them, but lodash has a function that does exactly this called partition.

_.partition([1, 2, 3], function(n) {
  return n % 2;
});
// → [[1, 3], [2]]

_.partition([1.2, 2.3, 3.4], function(n) {
  return this.floor(n) % 2;
}, Math);
// → [[1.2, 3.4], [2.3]]

Creates an array of elements split into two groups, the first of which contains elements predicate returns truthy for, while the second of which contains elements predicate returns falsey for. The predicate is bound to thisArg and invoked with three arguments: (value, index|key, collection).

If a property name is provided for predicate the created _.property style callback returns the property value of the given element.

If a value is also provided for thisArg the created _.matchesProperty style callback returns true for elements that have a matching property value, else false.

If an object is provided for predicate the created _.matches style callback returns true for elements that have the properties of the given object, else false. Arguments

  1. collection (Array|Object|string): The collection to iterate over.
  2. [predicate=_.identity] (Function|Object|string): The function invoked per iteration.
  3. [thisArg] (*): The this binding of predicate.

Returns

(Array): Returns the array of grouped elements.

More examples

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

var mapper = function(array) {
  return _.pluck(array, 'user');
};

// using the `_.matches` callback shorthand
_.map(_.partition(users, { 'age': 1, 'active': false }), mapper);
// → [['pebbles'], ['barney', 'fred']]

// using the `_.matchesProperty` callback shorthand
_.map(_.partition(users, 'active', false), mapper);
// → [['barney', 'pebbles'], ['fred']]

// using the `_.property` callback shorthand
_.map(_.partition(users, 'active'), mapper);
// → [['fred'], ['barney', 'pebbles']]
like image 130
John Avatar answered Oct 02 '22 05:10

John


In this case, you're better off with forEach, but I address your question about why !predicate didn't work (and how you can make something like it that does) below as well.

First, the simple forEach solution:

Prosiac:

function test(array, predicate){
    var filterTrue = [];
    var filterFalse = [];
    array.forEach(function(value) {
        if (predicate(value)) {
            filterTrue.push(value);
        } else {
            filterFalse.push(value);
        }
    });
    // rest of method
}

A bit more terse:

function test(array, predicate){
    var filterTrue = [];
    var filterFalse = [];
    array.forEach(function(value) {
        (predicate(value) ? filterTrue : filterFalse).push(value);
    });
    // rest of method
}

As an aside, I have tried:

var filterFalse = array.filter(!predicate);

But this is doesn't seem to work for reasons I'm still trying to understand

It would have to be:

var filterFalse = array.filter(function(entry) {
    return !predicate(entry);
});

...and that would indeed work, but it means you're making two passes through the array and calling the predicate twice for every element. That's why I recommended forEach: Only one pass through the array is required, and only one call to the predicate for each entry.

The reason your var filterFalse = array.filter(!predicate); didn't work is that it's taking the predicate variable, which contains a reference to a function, and logically inverting it with !. The logically-inverted version of a non-null object reference (functions are objects) is false, so you were in effect passing false into filter.

More completely: The unary ! coerces its operand to a boolean value and then returns the opposite of it (false for true and true for false). So !predicate will result in false for any value of predicate that coerces to true (aka "truthy" values), and will result in true for any value of predicate that coerces to false (aka "falsey" values). So what are the "truthy" and "falsey" values? The "falsey" values are 0, "", null, undefined, NaN, and of course, false; the "truthy" values are all others, including all non-null object references.

If you program with predicates a lot and want a way to say "not predicate" and get a function that gives you the inverted result, you can do that like this:

function not(predicate) {
    return function() {
        return !predicate.apply(this, arguments);
    };
}

and then:

var filterFalse = array.filter(not(predicate));

That not function works like this: It returns a new function that, when called, will call the predicate function you gave it, passing along the this value it was called with and all of the arguments it was called with (via Function#apply — spec | MDN), logically inverting the return value it gets from the predicate, and then returning that inverted value.

But again, using that would require two passes through the array. Sometimes with high-level abstractions, though, that could be preferable to the forEach solution.

Finally, if you do this "either/or" thing a lot, you could of course make a function to do that:

function divvyUp(array, predicate) {
    var filterTrue = [], filterFalse = [];
    array.forEach(function(value) {
        (predicate(value) ? filterTrue : filterFalse).push(value);
    });
    return {
        filterTrue: filterTrue,
        filterFalse: filterFalse
    };
}
like image 28
T.J. Crowder Avatar answered Oct 02 '22 04:10

T.J. Crowder