11

I've got two arrays in Javascript which currently look like this, but are updated by HTTP requests (node):

var x = [[292,"2349","902103","9"],[3289,"93829","092","920238"]]
var y = [[292,"2349","902103","9"],[322,"93829","092","920238"],[924,"9320","8932","4329"]]

I'm looking to compare these arrays, so that, if there is an array inside y that is not in x, it will be saved to a new array - z. Note that sometimes the order of arrays inside the arrays will change, but I would not like this to affect the result.

If there is an array inside x that is not in y, however, is should not be saved to z.

I read JavaScript array difference and have been able to replicate this, but if the x array is not shown in y, it is printed to z. I am wondering if it is possible for this not to be stored, only the different items in y?

Community
  • 1
  • 1
GregW
  • 173
  • 6
  • Will the inner arrays always contain strings & numbers ? – elad.chen Apr 09 '16 at 17:33
  • They will always be in this format (always in the same order): [int,"str","str","str"]. – GregW Apr 09 '16 at 17:34
  • so the ints need to be different? is that enough ? – omarjmh Apr 09 '16 at 17:35
  • If the array in `y` is not in `x` save array to `z`? – guest271314 Apr 09 '16 at 17:38
  • @JordanHendrix Anything in the array being different should save this array to the new array. Some arrays often have the same initial integer. – GregW Apr 09 '16 at 17:39
  • @guest271314 Yes, correct. – GregW Apr 09 '16 at 17:40
  • 1
    Possible duplicate of [How to compare arrays in JavaScript?](http://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript) – The Reason Apr 09 '16 at 17:54
  • 1
    This is NOT a duplicate. The [How to compare arrays in JavaScript?](http://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript) question asks about comparing arrays which contains only primitives, like numbers or strings, whereas this question asks about comparing arrays which contain other arrays. – Michał Perłakowski Apr 09 '16 at 18:49

6 Answers6

9

Use a higher-order function that accepts an array (which changes with each iteration of y) and returns a new function that operates on each element (nested array) in some. It returns true if the arrays contain the same elements regardless of order.

function matches(outer) {
  return function (el) {
    if (outer.length !== el.length) return false;
    return el.every(function (x) {
      return outer.indexOf(x) > -1;
    });
  }
}

Iterate over y and return a list of arrays that aren't in x.

function finder(x, y) {
  return y.filter(function (el) {
    return !x.some(matches(el));
  });
}

finder(x, y);

DEMO

Andy
  • 61,948
  • 13
  • 68
  • 95
  • Is the `.reduce` in `finder` any different from a `.filter`? – andyk Apr 10 '16 at 02:49
  • Not really in this case. I added an edit to my answer. – Andy Apr 10 '16 at 02:56
  • This might not affect the OP's use case, but your `matches` function will return true for two subarrays that have items in different order: `matches([1, '2', '3'])(['3', 1, '2']) === true`. It will also return true if the first array has extra items in it: `matches([1, 4, '2', '3', 5, '6'])(['3', 1, '2']) === true`. – Noah Freitas Apr 10 '16 at 17:35
  • Not worried about the first point as the OP clarified that in his comments, but you're right about your second point and I've updated my code to reflect that, cheers @NoahFreitas. – Andy Apr 10 '16 at 17:44
  • You can also use [`Array.prototype.includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) instead of `.indexOf()` to determine whether an array contains specific element. – Michał Perłakowski Apr 12 '16 at 17:15
  • @Andy This appears to print out "true", rather than the concerned arrays. Could you suggest a way of modifying it to give the concerned arrays? – GregW Apr 15 '16 at 20:37
  • @GregW, no it doesn't. Look at the demo - it returns an array of results. – Andy Apr 15 '16 at 20:49
3

You can use this function arrayDiff.

It takes two arrays (A and B) and returns an array of all elements that are in the first array and not in the second (A \ B), with any duplicates removed. Two array elements are equal if their JSON serialization is the same.

var x = [[292,"2349","902103","9"],[3289,"93829","092","920238"]];
var y = [[292,"2349","902103","9"],[322,"93829","092","920238"],[924,"9320","8932","4329"]];

var z = arrayDiff(y, x);

// z is [[322,"93829","092","920238"],[924,"9320","8932","4329"]]

// arrayDiff :: [a], [a] -> [a]
function arrayDiff(a1, a2) {
    let a1Set = toStringSet(a1),
        a2Set = toStringSet(a2);

    return Array.from(a1Set)
                .filter(jsonStr => !a2Set.has(jsonStr))
                .map(JSON.parse);

    // toStringSet :: [a] -> Set<String>
    function toStringSet(arr) {
        return new Set(arr.map(JSON.stringify));
    }
}
Noah Freitas
  • 17,240
  • 10
  • 50
  • 67
2

This should work even if the order in the inner arrays is different.
I'm assuming you will have only numbers and strings in there and you don't expect a strict comparison between them.

var x = [[292,"2349","902103","9"],[3289,"93829","092","920238"]];
var y = [[292,"2349","902103","9"],[322,"93829","092","920238"],[924,"9320","8932","4329"]];

// this will do y \ x
var z = arrDiff(y, x);
console.log(z);


function arrDiff(arr1, arr2) {
  var rez = [];

  for (var i = 0; i < arr1.length; i++) {
    if ( ! contains(arr2, arr1[i])) {
      rez.push(arr1[i]);
    }
  }

  return rez;
}

function contains(arr, x) {
  x = x.slice().sort().toString();
  for (var i = 0; i < arr.length; i++) {
    // compare current item with the one we are searching for        
    if (x === arr[i].slice().sort().toString()) {
      return true;
    }
  }

  return false;
}
Mihai Vilcu
  • 1,947
  • 18
  • 24
1

Try this:

function getArraysDiff(arr1, arr2) {
   var x = arr1.map(function(a) { return a.join("") });
   var y = arr2.map(function(a) { return a.join("") });
   var z = [];

   for ( var i = 0, l = arr1.length; i < l; i++ ) {
      if ( y.indexOf(x[i]) == -1 ) {
        z.push(arr1[i])
      }
   }

   return z;
}

Or this:

x.filter((function(y) {
    return function(x) {
        return y.indexOf(x.join("")) > -1;
    }
}( y.map(function(y) { return y.join("") }) )))
elad.chen
  • 2,375
  • 5
  • 25
  • 37
1

You can use Array.prototype.forEach(), Array.prototype.every(), Array.prototype.map(), Array.prototype.indexOf(), JSON.stringify(), JSON.parse()

var z = [];
y.forEach(function(val, key) {
  var curr = JSON.stringify(val);
  var match = x.every(function(v, k) {
    return JSON.stringify(v) !== curr
  });
  if (match && z.indexOf(curr) == -1) z.push(curr)
});

z = z.map(JSON.parse);

var x = [
  [292, "2349", "902103", "9"],
  [3289, "93829", "092", "920238"]
];
var y = [
  [292, "2349", "902103", "9"],
  [322, "93829", "092", "920238"],
  [924, "9320", "8932", "4329"]
];

var z = [];
y.forEach(function(val, key) {
  var curr = JSON.stringify(val);
  var match = x.every(function(v, k) {
    return JSON.stringify(v) !== curr
  });
  if (match && z.indexOf(curr) == -1) z.push(curr)
});

z = z.map(JSON.parse);

console.log(z);

document.querySelector("pre").textContent = JSON.stringify(z, null, 2)
<pre></pre>
guest271314
  • 1
  • 15
  • 104
  • 177
1

You have got 2 arrays:

var x = [[292,"2349","902103","9"],[3289,"93829","092","920238"]];
var y = [[292,"2349","902103","9"],[322,"93829","092","920238"],[924,"9320","8932","4329"]];

To create the Z array, you need the following function:

function createZ(){
  var i,j,k=0,z=[],p=x;
  for(j=0;j<y.length;j++){
    for(i=0;i<p.length;i++){
      if(y[j][0]===p[i][0] && y[j][1]===p[i][1] && y[j][2]===p[i][2] && y[j][3]===p[i][3]){
        p.splice(i,1); break;
      } else {
        z[k++]=y[j]; console.log((y[j][0]===p[i][0])+" "+i+","+j);
      }
    }
  }
  return z;
}

Note that the createZ() also prints out the i,j of corresponding entry to the console.

Andy
  • 61,948
  • 13
  • 68
  • 95
Subhodip
  • 19
  • 3