views:

95

answers:

2

I have an array of items (terms), which will be put as <option> tags in a <select>. If any of these items are in another array (termsAlreadyTaking), they should be removed first. Here is how I have done it:

    // If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
    for (var i = 0; i < terms.length; i++)
    {
        for (var iAlreadyTaking = 0; iAlreadyTaking < termsAlreadyTaking.length; iAlreadyTaking++)
        {
            if (terms[i]['pk'] == termsAlreadyTaking[iAlreadyTaking]['pk'])
            {
                terms.splice(i, 1); // remove terms[i] without leaving a hole in the array
                continue;
            }
        }
    }    

Is there a better way to do this? It feels a bit clumsy.

I'm using jQuery, if it makes a difference.

UPDATE Based on @Matthew Flaschen's answer:

// If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
var options_for_selector = $.grep(all_possible_choices, function(elem)
                           {
                                var already_chosen = false;
                                $.each(response_chosen_items, function(index, chosen_elem)
                                {
                                    if (chosen_elem['pk'] == elem['pk'])
                                    {
                                        already_chosen = true;
                                        return;
                                    }
                                });
                                return ! already_chosen;
                           });

The reason it gets a bit more verbose in the middle is that $.inArray() is returning false, because the duplicates I'm looking for don't strictly equal one another in the == sense. However, all their values are the same. Can I make this more concise?

+3  A: 
var terms = $.grep(terms, function(el)
            {
              return $.inArray(el, termsAlreadyTaking) == -1;
            });

This still has m * n performance (m and n are the lengths of the arrays), but it shouldn't be a big deal as long as they're relatively small. To get m + n, you could use a hashtable

Note that ECMAScript provides the similar Array.filter and Array.indexOf. However, they're not implemented in all browsers yet, so you would have to use the MDC implementations as a fallback. Since you're using jQuery, grep and inArray (which uses native indexOf when available) are easier.

EDIT:

You could do:

var response_chosen_pk = $.map(response_chosen_items, function(elem)
{
  return elem.pk;
});
var options_for_selector = $.grep(all_possible_choices, function(elem)
{
  return $.inArray(elem.pk, response_chosen_pk) == -1;
});
Matthew Flaschen
Does IE support `indexOf` on arrays?
Matchu
Found it - [works in all but IE6](http://stackoverflow.com/questions/143847/best-way-to-find-an-item-in-a-javascript-array/143863#143863). Should probably just use `$.inArray` to avoid the question.
Matchu
@Matchu, you're right. I changed to inArray (though you could use the MDC implementation as a fallback too).
Matthew Flaschen
@Matchu, `indexOf` doesn't work for Array objects on *any* IE version, it has just been implemented on IE9 Platform Preview 3 which really looks promising...
CMS
@CMS - hmm, that's what I thought, too, but the linked answer said differently. Guess we've found ourselves an error - thanks :)
Matchu
What if the elements in the arrays don't strictly equal each other in the `==` sense, but they have the same values? As in `{'f': 'b'} == {'f': 'b'}` returns `false`?
Rosarch
@Rosarch, I added a solution for that. It generates an array containing only the pk values, then uses `inArray`.
Matthew Flaschen
A: 

http://github.com/danstocker/jorder

Create a jOrder table on termsAlreadyTaking, and index it with pk.

var table = jOrder(termsAlreadyTaking)
    .index('pk', ['pk']);

Then you can search a lot faster:

...
if ([] == table.where([{ pk: terms[i].pk }]))
{
    ...
}
...
Dan Stocker