1

I need to find a missing array in an "array of arrays". I started by finding this function below (on StackOverflow):

function findDeselectedItem(CurrentArray, PreviousArray) {

   var CurrentArrSize = CurrentArray.length;
   var PreviousArrSize = PreviousArray.length;
   var deselectedItem = [];

   // loop through previous array
   for(var j = 0; j < PreviousArrSize; j++) {

      // look for same thing in new array
      if (CurrentArray.indexOf(PreviousArray[j]) == -1)
         deselectedItem.push(PreviousArray[j]);

   }

   return deselectedItem;
}

This works just fine if you did something like this:

oldarray = ["hi", "ho", "hey"];
newarray = ["hi", "hey"];

Using findDeselectedItem(newarray, oldarray) would return ["ho"].

However, my content looks like this:

oldarray = [["James", 17, 1], ["Olivia", 16, 0], ["Liam", 18, 1]];
newarray = [["Olivia", 16, 0], ["James", 17, 1]];

How can I adapt the function above so that it returns the missing array containing 'Liam'.

Thanks

jskidd3
  • 4,609
  • 15
  • 63
  • 127
  • In case of checking array of arrays indexOf will return a positive or zero value only if references of arrays are same. It wont do a comparison by value – prasun Jul 27 '13 at 12:05

6 Answers6

2

I would make a hash with the name as a key. That would make finding missing content trivial and very fast. You can then optimize the method by not rebuilding the hash every time, but only when it's really necessary.

var oldArray = [["James", 17, 1], ["Olivia", 16, 0], ["Liam", 18, 1]];
var newArray = [["Olivia", 16, 0], ["James", 17, 1]];

function findDeselectedItems(oldArray, newArray)
{
   var results = [];

   var hash = {};
   for (var i=0; i<newArray.length; i++) {          
      hash[newArray[i].join(',')] = true;
   }

   for (var i=0; i<oldArray.length; i++) {
      if (!hash[oldArray[i].join(',')]) {
        results.push(oldArray[i]);
      }   
   }

   return results;
}   
Hashbrown
  • 12,091
  • 8
  • 72
  • 95
Michael Antipin
  • 3,522
  • 17
  • 32
  • be careful, (until my edit gets accepted) the current algorithm only checks the first element of each array. so ['bob', 1, 1] will be 'found' in the array of arrays; [['bob', 2, 2]] – Hashbrown Jul 27 '13 at 12:23
  • @Hashbrown To overcome dependency on name same algo can be optimized - hash[newArray[i].toString()] = true; and comparisons would become if (hash[oldArray[i].toString()] == null) – prasun Jul 27 '13 at 12:27
  • Ok, edit accepted. But I don't see how `!has[key]` is any better than `hash[key] == null`. I would prefer an obvious comparison and not implicit conversion of `null` to a Boolean value. – Michael Antipin Jul 27 '13 at 12:31
  • thanks, `==null` was an implicit conversion from `undefined`. Remember boolean ! || && operators do not convert anything, exemplified by (1 || 2) == 1, not true. ! is a valid way of doing things without implicities – Hashbrown Jul 27 '13 at 12:33
  • @parsun, true, that was what I proposed (except I forgot `toString()` inserted commas, I didnt want ['bob', 1, 1] to hash as 'bob11') – Hashbrown Jul 27 '13 at 12:35
  • @NoxNoctis Hi again. Would you mind making a change to this code for me? It currently also returning arrays that have changed in their content, would you be able to stop this? I need it to only return arrays that aren't in the oldArray. Thanks. – jskidd3 Jul 28 '13 at 22:00
  • Well, this will be of no difficulty once you tell me what do YOU mean by "the same" array. :) The code that was first proposed returned the first array with the missing name encountered. The second edition returns all "missing" arrays using all of the content as a key. What exactly is the desired behaviour? – Michael Antipin Jul 29 '13 at 07:12
  • @Hashbrown It's true for `||`, but not true for `!`. Try `var test = !null;` or `var test = !anything_not_boolean;` and see its type. Besides, [this is what ECMA spec expects the `!` operator to do](http://www.ecma-international.org/ecma-262/5.1/#sec-11.4.9). – Michael Antipin Jul 30 '13 at 08:12
  • @Hashbrown And on the contrary, comparing `undefined` with `null` does not convert anything. See [11.9.3 The Abstract Equality Comparison Algorithm](http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3): `3. If x is undefined and y is null, return true.` and [8.7.1 GetValue (V)](http://www.ecma-international.org/ecma-262/5.1/#sec-8.7.1). – Michael Antipin Jul 30 '13 at 08:26
1

The problem may be that indexOf uses strict equality. I.e. if an item in the 'previous' array isn't literally also in the 'current' array, it will report it to not be in there.

You will have to iterate over the values yourself (instead of using indexOf) and check if the array contains something that is 'the same as' (but not literally the same) the array.

I.e. if I didn't explain myself well enough take a look at this;

['bob'] == ['bob']; //false
//therefore
[['bob']].indexOf(['bob']); //-1
Hashbrown
  • 12,091
  • 8
  • 72
  • 95
  • I upvoted @NoxNoctis' answer. This 'answer' is more a reason behind it. His answer rectified your problem by evaluating the arrays as strings and comparing them – Hashbrown Jul 27 '13 at 12:16
  • Thanks for your answer, you did teach me something new about indexOf :-) – jskidd3 Jul 27 '13 at 12:18
  • send me an upvote then :) SO had a good system in place for all kinds of answers :P – Hashbrown Jul 27 '13 at 12:21
0

I hope that this helps you,

function findDeselectedItem(CurrentArray, PreviousArray) {

    var CurrentArrSize = CurrentArray.length;
    var PreviousArrSize = PreviousArray.length;
    var deselectedItem = [];

    // loop through previous array
    for(var j = 0; j < PreviousArrSize; j++) {
        var checkArray = PreviousArrSize[j];
        // loop through 2nd array to match both array

        for(var i = 0; i < CurrentArrSize; i++) {
            // look for same thing in new array
            if (CurrentArray[i].indexOf(checkArray) == -1)
                deselectedItem.push(CurrentArray[i]);

        }
    }
    return deselectedItem;
}
KarelG
  • 5,176
  • 4
  • 33
  • 49
0

@KarelG: nice and quick solution but should it not be var checkArray = PreviousArr[j]; instead of var checkArray = PreviousArrSize[j]; ?

Carsten Massmann
  • 26,510
  • 2
  • 22
  • 43
  • Welcome to StackOverflow, this answer should be a comment, however. Also, KarelG's solution didn't work, but your solution didn't fix it either. – jskidd3 Jul 27 '13 at 12:10
  • Hi Joel, I would have loved to put my remark in a comment, but the settings (I am a novice here and don't have a "reputation">50) unfortunately don't allow it. :-/ So far I can only comment on my own posts :-( – Carsten Massmann Jul 27 '13 at 12:14
  • Oh my apologies, sounds like something SO should look into. Thanks anyway :) – jskidd3 Jul 27 '13 at 12:15
0
function findDeselectedItem(CurrentArray, PreviousArray) {

    var CurrentArrSize = CurrentArray.length;
    var PreviousArrSize = PreviousArray.length;
    var deselectedItem = [];
    var selectedIndices = [];

  // loop through previous array
  for(var j = 0; j < PreviousArrSize; j++) {

   for(k=0; k < CurrentArrSize ; k++){
      if (CurrentArray[k].toString() === PreviousArray[j].toString()){
          selectedIndices.push(j);
          break;
      }

   }

 }

    for(var l = 0; l < PreviousArrSize; l++){
      if(selectedIndices.indexOf(l) === -1){
         deselectedItem.push(PreviousArray[l]);
      }
    }

      return deselectedItem;
}
prasun
  • 7,073
  • 9
  • 41
  • 59
0

I don't think you can use indexOf to compare two arrays. You need a deeper comparison. Although this code could be written another way, you could do this with an array comparison function and using Array.some() to filter through your elements. Here's an example and a fiddle;

// Credit http://stackoverflow.com/questions/7837456/comparing-two-arrays-in-javascript
// attach the .compare method to Array's prototype to call it on any array
Array.prototype.compare = function (array) {
    // if the other array is a falsy value, return
    if (!array)
        return false;

    // compare lengths - can save a lot of time
    if (this.length != array.length)
        return false;

    for (var i = 0; i < this.length; i++) {
        // Check if we have nested arrays
        if (this[i] instanceof Array && array[i] instanceof Array) {
            // recurse into the nested arrays
            if (!this[i].compare(array[i]))
                return false;
        }
        else if (this[i] != array[i]) {
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;
        }
    }
    return true;
}

function findDeselectedItem(CurrentArray, PreviousArray) {

    var CurrentArrSize = CurrentArray.length;
    var PreviousArrSize = PreviousArray.length;
    var deselectedItem = [];

    // loop through previous array
    for (var j = 0; j < PreviousArrSize; j++) {
        // look for same thing in new array
        CurrentArray.some(function (a, idx) {
            if(PreviousArray[j].compare(a) == false) {
                deselectedItem.push(PreviousArray[j]);
                return true;
            }
        });
    }

    return deselectedItem;
    }

var oldarray =[["James", 17, 1], ["Olivia", 16, 0], ["Liam", 18, 1]];
var newarray =[["Olivia", 16, 0], ["James", 17, 1]];

console.log(findDeselectedItem(newarray, oldarray));
cirrus
  • 5,624
  • 8
  • 44
  • 62