Using Array.filter with multiple conditions

I am faced with a scenario where I need to filter and categorize an array of objects based on multiple conditions. The challenge arises from the fact that there are more than one condition, and I want the array to be split into several groups accordingly. Each group should match a specific condition, with the last group containing all objects that do not meet any of the conditions.

Initially, my approach involved using multiple .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 nor 20
var matchedNoConditional = array.filter(function(x){
  return (x.age !== 18 && x.age !== 20);
});

However, I found this approach redundant and not very reusable at all.

So, I decided to modify the function based on Brendan's answer, resulting in the following solution.

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 new method returns an array with three arrays. The first array contains objects matching the first condition, the second array contains objects matching the second condition, and the last array contains objects that do not fit into either condition.

Although this solution works well for situations with only two conditions, it is limited in its reusability for scenarios requiring more than two conditions.

I am seeking a solution that allows me to specify any number of conditions and receive the corresponding arrays along with an additional array for unmatched objects.

Note: The input and output formats do not necessarily have to be arrays, but for clarity, I chose to represent them as such. The method does not have to follow the .filter model; it could also be implemented as a .map or .reduce function. Any suggestions are welcome.

Edit: Following @slebetman's suggestion, it would be beneficial if the solution supported code composability.

Answer №1

We will utilize the findIndex method to locate the index of the condition that matches, and then place the element in the corresponding array element of the output:

function createGrouper(...conditions) {

  return function(array) {
    // Create 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;
  };

}

Alternatively, if you prefer using reduce:

function createGrouper(...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([]));  
  };

}

How to use it:

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

console.log(grouper(data));

This approach entails defining a function where you supply different filters, which then produces a function that allows you to perform the actual grouping.

Answer №2

Consider trying out this approach:

Array.prototype.categories = function(...filters) {
  return this.reduce(
    (categories, item) => {
      let indices = [];

      filters.forEach((filter, index) => {
        if (filter(item)) indices.push(index);
      });

      if (indices.length === 0) categories[categories.length - 1].push(item);
      else indices.forEach(index => categories[index].push(item));

      return categories
    }, 
    Array.apply(null, { length: filters.length + 1})
      .map(element => [])
  );
}

This method will categorize the items based on the specified conditions.

Example of how to use it:

array.categories(x => x.name === 'A', x => x.age >= 21);

The last category in the final array contains items that did not match any condition.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

``motioning a component beneath another component that may be in a state

Having an issue with my CSS. I have some elements generated by JavaScript and when hovering over them, another element is displayed below the others for some reason. Here's the CSS related to this problem: .hiddenTextjob { display:none; ...

Utilizing Firebase messaging onMessage function is exclusively enabled within a window environment

Incorporating Firebase Cloud Messaging into my project allowed me to send and receive push notifications successfully. While I can receive the push notifications, unfortunately, I am encountering issues with getting the notification events to function prop ...

During development, getStaticPaths and getStaticProps successfully function, however, the prop during build time becomes undefined

I am currently working on developing an eCommerce platform utilizing Next.js. One of the challenges I encountered was in the product page where dynamic routes are used. Specifically, I implemented getStaticProps to fetch the product and getStaticPaths to g ...

Ways to transfer a value between two different card elements

I have designed a component with three div cards side by side using bootstrap. The first card displays a list of products, and my intention is that when a product is clicked, the details should appear in the second card on the same page. However, this fun ...

Is it possible for AJAX JSON response to return both accurate and undefined values sporadically?

In the process of developing JavaScript code to pinpoint the smallest unused number within a specified range of numbers, I encountered an issue with the AJAX request. Despite successfully retrieving all used numbers in the designated range, some undefined ...

AngularFire: Retrieve the $value

Having an issue logging the service.title to the console as it keeps returning as a strange object. .factory('service', function($firebase, FBURL, $routeParams) { var ref = new Firebase(FBURL + "services/" + $routeParams.serviceId); var titl ...

The search for 'partition' in 'rxjs' did not yield any results

Recently, I attempted to incorporate ng-http-loader into my Angular project. After successfully installing the ng-http-loader package, I encountered an error during compilation. The specific error message displayed was: export 'partition' was ...

Only the initial upload file is being passed through the Apollo Express server, with the remaining files missing in action

Currently, I am utilizing the apollo-express server with GraphQL. One issue I am encountering involves a mutation where I pass files from the front-end to the back-end. Strangely, I receive the file:{} object only for the first file - for the others, I rec ...

Substitute placeholders in array with information using a loop

I have a question regarding implementing an autosort feature in JavaScript. I want my page to automatically sort data rows based on different time intervals selected by the user through checkboxes. The data updates every 3 seconds, and the autosort functio ...

What is the best way to dynamically add getJSON's data to a div whenever the loadmore button is clicked?

When a page loads, my getJSON function displays its output in a div called myDiv. Now, I am looking to add a button at the bottom of the page. When the user clicks this button, I want to trigger another call to getJSON. Each time the button is clicked, I ...

Update settings when starting with chromedriver

I am currently using webdriver (), standalone selenium, and mocha for writing my test cases. These test cases are specifically designed for Chrome, so I rely on chromedriver for execution. However, when launching the browser, I need to ensure that the "to ...

Is there a way to incorporate the req.setHeaders method with the res.redirect method in the same app.get function?

var express = require('express'); var app = express(); var PORT = process.env.PORT; app.get('/', function(req, res){ res.json('To search for images, enter your query parameters like this: https://api.cognitive.microsoft.com/bi ...

What is the purpose of exporting the metadata variable in the Layout.js file in Next.js?

I've recently started using next.js and I'm a bit confused about why the metadata variable is exported from the Layout file without being imported anywhere. import "./globals.css"; export const metadata = { title: "NextJS App&qu ...

Assigning values to template variables in Express 4's routes/index

Recently, I started using node.js and express. To set up express 4, I used the command "express myAppName" in the terminal, which created a default directory with Jade templates as default. The main file, app.js, has the following standard express boilerp ...

I am having trouble retrieving the properties of "2d" objects using tiles[i-1], unlike standard objects in JavaScript

I've been working on constructing a random map generator by utilizing a grid composed of tiles within a canvas. In my process, I'm investigating the properties of each tile tiles[i] before handling tiles[i-1]. While this procedure seems to functi ...

Unable to capture screenshot of hovered element using Cypress

Having an issue with taking a screenshot of an element with a hover effect. The screenshots always come out without the hover effect applied. tableListMaps.lineWithText('Hello world', 'myLine'); cy.get('@myLine').realH ...

Bootstrap UI Tab presents an obstacle for Google Map functionality

When utilizing the Bootstrap Tabset to include a Bootstrap slider, Google Map, and other pages, I encountered an issue where the slider functions perfectly but the Google Map does not work as expected. Interestingly, the map works perfectly in street view ...

Tips for syncing HTML Canvas to track the mouse's X and Y position accurately across various screen resolutions

I'm facing an issue with my canvas game where the onclick buttons are redirecting to another menu. However, when I open the Canvas on a monitor with a different resolution, all the X & Y coordinates change and nothing works as expected. Is there a wa ...

How does setting 0 as the initial element of an array prevent the execution of a "for" loop in JavaScript?

Take a look at the JavaScript code snippet below: var words = delIdx = [0, 1, 2, 3]; for(let i=0; delIdx[i]; i++) { console.log('DELIDX: ', delIdx[i]); } for(let i=0; words[i]; i++) { console.log('Word: ', words[i]); } The arrays ...

Tips for automatically closing SweetAlert after an AJAX request finishes

I recently implemented Sweet-alert into my Angular project. function RetrieveDataFromAPI(url) { SweetAlert.swal( { title: "", text: "Please wait.", imageUrl: "../../app/app-img/loading_spinner.gif", showConfirmB ...