0

I have a tags input box, similar to this except I need to restrict it to allowable combinations.

Here is an example of the possible combinations:

[{
      "Combo": [
        {
          "Id": 1,
          "Name": "Tag1"
        },
        {
          "Id": 3,
          "Name": "Tag3"
        }
      ]
    },
    {
      "Combo": [
        {
          "Id": 2,
          "Name": "Tag2"
        },
        {
          "Id": 3,
          "Name": "Tag3"
        },
        {
          "Id": 4,
          "Name": "Tag4"
        }
      ]
    }]

I start off by getting a distinct list of Tags and displaying them to the user. As tags are selected, I need to filter the tags by the passed combinations. Therefore, if I select Tag3, I should get the available tags of Tag1, Tag2 & Tag4. I was able to accomplish this by looping through the array of arrays and getting the index of the combos by the array of ids. Like so:

ids.indexOf(combos[a].Combo[c].Id) !== -1

However the issue is when I then add Tag2 to the array of ids, indexOf still includes the first combo because of the Id: 3. What I want is to find the combos that have matching or more Ids.

So when I pass this:

var ids = [3, 2];

I want this combo:

[{
      "Combo": [
        {
          "Id": 2,
          "Name": "Tag2"
        },
        {
          "Id": 3,
          "Name": "Tag3"
        },
        {
          "Id": 4,
          "Name": "Tag4"
        }
      ]
    }]

It is a bit messy, but here is the jsfiddle example I have been working on. http://jsfiddle.net/4L3kr052/

1
  • Can you elaborate on "I need to filter the tags by the passed combinations."? Commented Nov 17, 2015 at 2:20

3 Answers 3

1

I have created a fiddle solving this problem.

http://jsfiddle.net/4L3kr052/1/

var getAvailableTags = function (combos, ids) {
        var matched = []
        combos.forEach(function(comb){
            var keys = comb.Combo.map(function(d){
                return d.Id;
            }); 
            var found = 1;
            ids.forEach(function(id){
                found &= (keys.indexOf(id) !== -1);
            });
            if (found){
                matched.push(comb);
            }

        })
        return matched;
}
Sign up to request clarification or add additional context in comments.

Comments

0

To get the required combos, you need to filter them such that the given combo contains every id from the ids array.

var combos = [{
      "Combo": [
        {
          "Id": 1,
          "Name": "Tag1"
        },
        {
          "Id": 3,
          "Name": "Tag3"
        }
      ]
    },
    {
      "Combo": [
        {
          "Id": 2,
          "Name": "Tag2"
        },
        {
          "Id": 3,
          "Name": "Tag3"
        },
        {
          "Id": 4,
          "Name": "Tag4"
        }
      ]
    }];

function getCombos(combos, ids) {
    return combos.filter( // filter accepts combos that...
        function (g) {
            return ids.every( // ... contain every id in ids...
                function (id) {
                    return g.Combo.some( // ... such that the id is present within some combo.
                        function (c) {
                            return c.Id === id;
                        });
                });

        });
}

getCombos(combos, [3, 2]); // returns your desired combo
getCombos(combos, [3, 1]); // returns the first combo
getCombos(combos, [3, 5]); // returns an empty array

Comments

0

Solve one problem at a time,

First,

How to test a Combo for every id? Move the logic to test this into it's own function to make your life easier, for example

function comboHasIds(combo, ids) {
    var i, j;
    find_next: for (i = 0; i < ids.length; ++i) {
        for (j = 0; j < combo.length; ++j)
            if (combo[j].Id === ids[i])
                continue find_next;
        return false; // if we reach here then id[i] wasn't in combo
    }
    return true; // if we reach here then we ran out of ids to test for
}

Please notice the use of a label to let the nested loop continue the outer loop

Example usage on a single Combo is as follows

var ex = [
        {"Id": 2, "Name": "Tag2"},
        {"Id": 3, "Name": "Tag3"},
        {"Id": 4, "Name": "Tag4"}
    ];

comboHasIds(ex, [3, 1]); // false
comboHasIds(ex, [3, 2]); // true

As zerkms points out, as does Adeel's answer, this test is of the form

  1. For every id
  2. If there is some item in combo
  3. Which has Id property id
  4. Return true

Which could be written using Array.prototype methods every and some instead of nested loops, e.g. as one line in arrow functions

var comboHasIds = (combo, ids) => ids.every(id => combo.some(item => item.Id === id));

Second,

How to iterate over each Combo with this test? We have the handy Array.prototype.filter method just for this, so filtering with the above would look like

// var data = /* your data from above */;
var test = function (ids) {
        return function (e) {return comboHasIds(e.Combo, ids);};
    };

data.filter(test([3, 2]));
// [{"Combo": [
//   {"Id": 2,"Name": "Tag2"},
//   {"Id": 3,"Name": "Tag3"},
//   {"Id": 4, "Name": "Tag4"}
// ]}]

5 Comments

They said "to make your life easier" and then used labels and goto-like continue. That's funny :-)
@zerkms would you prefer I wrote "for example"? The key point of this answer is that OP can break the big problem down into two smaller problems which individually are easier to solve.
Don't know the exact better wording, the whole function just looks too tangled even for example. PS: es2015 oneliner: ids.every(id => combo.some(c => c.id === id))
@zerkms edited it to show this alternative way. Seriously though, is using break or continue that advanced? You haven't learnt loops if you haven't learnt them. I can understand labels being new to some people, though. They will probably become more used now ES6 let has block scope.
it's not continue or break that problematic, but unconditional jumps.

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.