简体   繁体   中英

Array.filter with more than one conditional

I have an array of objects which I would like to filter and divide into groups according to a conditional. The predicament comes because I have more than one conditional, and would like the array to be divided into several arrays. First array matching the first conditional, second array matching second conditional, ... , and the last array containing all objects not matching any conditional.

The first solution that came to mind to this problem was in the form of several .filter functions...

var array = [{
  name: 'X',
  age: 18
}, {
  name: 'Y',
  age: 18
}, {
  name: 'Z',
  age: 20
}, {
  name: 'M',
  age: 20
}, {
  name: 'W',
  age: 5
}, {
  name: 'W',
  age: 10
}];
//objects with age 18
var matchedConditional1 = array.filter(function(x){
  return x.age === 18;
});
//objects with age 20
var matchedConditional2 = array.filter(function(x){
  return x.age === 20;
});
//objects with neither age 18 or 20
var matchedNoConditional = array.filter(function(x){
  return (x.age !== 18 && x.age !== 20);
});

but that seemed redundant and not reusable at all.

So I modified the function on Brendan's answer, and got this.

Array.prototype.group = function(f) {
  var matchedFirst = [],
      matchedSecond = [],
      unmatched = [],
      i = 0,
      l = this.length;
  for (; i < l; i++) {
    if (f.call(this, this[i], i)[0]) {
      matchedFirst.push(this[i]);
    } else if (f.call(this, this[i], i)[1]) {
      matchedSecond.push(this[i]);
    } else {
      unmatched.push(this[i]);
    }
  }
  return [matchedFirst, matchedSecond, unmatched];
};
var filteredArray = array.group(function(x){
  return [x.age === 18, x.age === 20];
});

This method returns an array with 3 arrays. The first one containing all objects matching the first conditional, second one with objects matching the second conditional, and the last one with objects not matching any of the two.

The problem with this method is that it is limited to two conditionals and hence only three groups of objects. This method actually works for my particular situation for I only have two conditionals, but is not reusable in situations that require more than two.

I would like to be able to give as many conditionals as I want and receiving that amount of arrays plus an extra array with objects not belonging to any group.

Ps. The input and output don't need to be arrays, but I thought that makes more sense. The method doesn't have to be modeled after .filter, it very well could be a .map function or even a .reduce. Any suggestion is appreciated.

Edit: as suggested by @slebetman, it would be great if the answer allowed for code composability.

Try something like this:

Array.prototype.groups = function(...conditions) {
  return this.reduce(
    (groups, entry) => {
      let indices = [];

      conditions.forEach((cond, i) => {
        if (cond(entry)) indices.push(i);
      });

      if (indices.length === 0) groups[groups.length - 1].push(entry);
      else indices.forEach(i => groups[i].push(entry));

      return groups
    }, 
    Array.apply(null, { length: conditions.length + 1})
      .map(e => [])
  );
}

In this solution if an entry matches more than one condition it will appear in corresponding number of groups.

Usage example: array.groups(x => x.name === 'X', x => x.age === 18);

The last element in a final array - unmatched entries.

We'll use findIndex to find the index of the condition which matches, and put the element in the corresponding array element of the output:

function makeGrouper(...conditions) {

  return function(array) {
    // Make an array of empty arrays for each condition, plus one.
    var results = conditions.map(_ => []).concat([]);

    array.forEach(elt => {
      var condition = conditions.findIndex(condition => condition(elt));
      if (condition === -1) condition = conditions.length;
      results[condition].push(elt);
    });

    return results;
  };

}

Or, if you are a fan of reduce :

function makeGrouper(...conditions) {

  return function(array) {
    return array.reduce((results, elt) => {
      var condition = conditions.findIndex(condition => condition(elt));
      if (condition === -1) condition = conditions.length;
      results[condition].push(elt);
      return results;
    }, conditions.map(_ => []).concat([])));  
  };

}

Usage:

const grouper = makeGrouper(
  elt => elt.age === 18,
  elt => elt.age === 20
);

console.log(grouper(data));

This solution involves defining a function to which you provide the various filters, which returns a function which you can then use to do the actual grouping.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM