Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering array against all of another array

The $scope.categories array is being populated from a multi-select element in AngularJS.

$scope.categories = ["Adventure", "Strategy"]

I need to compare this array against the categories array in the items array below:

$scope.items = [
    {
        title: "Star Wars",
        categories: ["Adventure"]
    }, {
        title: "Star Search",
        categories: ["Adventure", "Strategy"]
    }, {
        title: "Star Trek",
        categories: ["Adventure", "Family"]
    }, {
    title: "Star Wars",
    categories: ["Family", "Strategy"]
}];

Both values in $scope.categories need to match the same values in $scope.items.categories, for the object to be pushed to an output array.

With the resulting $scope.filtered array being (items1):

{
  title: "Star Search",
  categories: ["Adventure", "Strategy"]
}

I have the logic until the loop needs to reiterate... but how?

  1. I am looping through $scope.categories
  2. Then looping through $scope.items
  3. Then looping through $scope.item.categories array of each object.
  4. Then I am comparing the $scope.categories value against the value of $scope.item.categories

    for (var i = 0; i < categories.length; i++) {
      for (var j = 0; j < items.length; j++) {
        for (var k = 0; k < items[j].categories.length; k++) {
          if(categories[i] == items[j].categories[k]) {
            console.log("The value of " + categories[i] + ", matches " + items[j].categories[k]);
          } else {
            console.log("The value of " + categories[i] + ", is not a match");
          }
        }
      }
    }
    

Here is a JSbin example

like image 674
John Spiteri Avatar asked Feb 09 '23 10:02

John Spiteri


2 Answers

It's much simpler really:

var filtered = items.filter(function(i) {
  return categories.every(function(c) {
    return i.categories.indexOf(c) >= 0
  })
})

Array.prototype.filter iterates an array and calls a callback function for each item. Each item that the callback function returns true for is included in the result array.

Array.prototype.every iterates an array and calls a callback function for each item, returning true if the callback for ALL items returned true, and false otherwise.

In this use case, we're filtering the items array, and the filter callback returns true when all of the categories "pass" the every callback condition, which is that the item categories contain the current (c) category.

(Here's the fixed JSBin)

like image 176
Amit Avatar answered Feb 12 '23 00:02

Amit


Here's a bit simpler solution. It assumes that ordering and capitalization are precise. Interestingly, filter + join solution outperforms the filter + every method. If top-level categories (i.e., var categories = ["Adventure", "Strategy"]) is assumed to be a subset, then join() function can be combined with indexOf. The solution would still be simpler and performance is still slightly better.

See performance test here: http://jsfiddle.net/t3g3k7tL/.

var categories = ["Adventure", "Strategy"];

var items = [
    {
        title: "Star Wars",
        categories: ["Adventure"]
    }, {
        title: "Star Search",
        categories: ["Adventure", "Strategy"]
    }, {
        title: "Star Trek",
        categories: ["Adventure", "Family"]
    }, {
        title: "Star Wars",
        categories: ["Family", "Strategy"]
    }
];

var filtered = items.filter(function(element) {
    return element.categories.join("") === categories.join("");  
});

If, top level categories is a subset and not exact match, then the following solution will work:

var filtered = items.filter(function(element) {
    return element.categories.join("").indexOf(categories.join("")) !== -1;    
});
like image 45
DRD Avatar answered Feb 11 '23 22:02

DRD