4

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

5
  • @Mousey, I did not know about the other tags... Thank you Commented Jul 29, 2015 at 21:17
  • 2
    Maybe create a hash of each array and compare the hashes? (@Amit's answer is better.) Commented Jul 29, 2015 at 21:18
  • @Kutyel, I am using this in an AngularJS filter, but was trying to simplify and isolate the issue... is that better or worse? Commented Jul 29, 2015 at 21:28
  • 1
    @JohnSpiteri I also think that is always good keeping things simple, but in stackoverflow only javascript related questions are too general and IMHP an angularjs tag would attract more atenttion on the question, besides the fact that someone without angular knowledge would probably be mad about the $scope staff... just an opinion ;) Commented Jul 30, 2015 at 6:23
  • @Kutyel, I agree with your opinion - will update my approach. Thank you Commented Jul 30, 2015 at 12:57

2 Answers 2

7

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)

Sign up to request clarification or add additional context in comments.

4 Comments

Could you please explain the steps @Amit? Genius is simple.
@tymeJV I used these identifier since that's how they're referenced in the question, as well as in the JSBin. Please don't "correct" what isn't wrong.
So short, proper and slick. Thank you @Amit
@Amit Thank you so much for this answer! I was trying to achieve a very similar thing in react and this was almost exactly the function i needed to use. Very elegant, too.
1

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

1 Comment

I appreciate your approach that also works, and will compare the answers once I get a chance - obviously the more efficient answer is best. Thank you

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.