John Spiteri
John Spiteri

Reputation: 594

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

Upvotes: 4

Views: 107

Answers (2)

DRD
DRD

Reputation: 5813

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;    
});

Upvotes: 1

Amit
Amit

Reputation: 46323

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)

Upvotes: 7

Related Questions