4

Is there a way to return the difference between two arrays in JavaScript? I can not use indexOf in this case.

For example:

var a1 = [{"a":"A"},{"b":"B"}];
var a2 = [{"a":"A"},{"b":"B"},{"c":"C"}];

// need [{"c":"C"}]

Please advise.

1
  • 3
    Strictly speaking, the "difference" is the entire array, because two objects are only equal if they refer to the same instance, which these literals do not. Commented Mar 5, 2014 at 9:52

4 Answers 4

2

One object can never be the same as another object even if they have the same content. They would still be different instances of Objects.

That means you have to compare keys and values to check that they match, or in this case, don't match.

var a1 = [{"a":"A"},{"b":"B"}];
var a2 = [{"a":"A"},{"b":"B"},{"c":"C"}];

var a3 = a2.filter(function(o) {
    return Object.keys(o).some(function(k) {
        return a1.every(function(o2) {
            return !(k in o2) || (o2[k] != o[k]);
        });
    });
});

FIDDLE

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

1 Comment

@Johan - yes, for sets with large arrays it's probably not the best, but I don't see any other way to compare objects than simply iterating over all of them.
2

As I mentioned in my comment, objects are only equal if they refer to the same instance. Therefore, any built-in system will not do, least of all == and ===. So, first you must define your own comparison function.

Let's say that two objects are equal if they contain the same keys with the same values.

function areObjectsEqual(a,b) {
    function helper(a,b) {
        var k;
        for( k in a) {
            if( a.hasOwnProperty(k)) {
                if( !b.hasOwnProperty(k)) return false;
                if( typeof a[k] != typeof b[k]) return false;
                if( typeof a[k] == "object") {
                    if( !areObjectsEqual(a[k],b[k])) return false;
                    // the above line allows handling of nested objects
                }
                else {
                    if( a[k] != b[k]) return false;
                    // this comparison is technically strict
                    // because we already checked typeof earlier
                }
            }
        }
    }
    return helper(a,b) && helper(b,a);
}

Okay, now that that's out of the way, we can compare our functions.

function array_diff(a,b) {
    var result = [], l = a.length, i, m = b.length, j;
    outer:
    for( i=0; i<l; i++) {
        for( j=0; j<m; j++) {
            if( typeof a[i] != typeof b[j]) continue;
            if( typeof a[i] == "object") {
                if( !areObjectsEqual(a[i],b[j])) continue;
            }
            else {
                if( a[i] != b[j]) continue;
            }
            // if we got to here, it's a match!
            // ... so actually we want to skip over the result :p
            continue outer;
        }
        // okay, if we get HERE then there was no match,
        // because we skipped the "continue outer"
        result.push(a[i]);
    }
    return result;
}

And there you go!

5 Comments

You need to add a closing braket on the array_diff check of areObjectsEqual
And presumably return result at the end of array_diff, although that doesn't give me the correct answer either.
I probably went wrong somewhere... I dunno. It's too early in the morning! XD Oh, but make sure you pass the longer array first - this function returns all elements in a that are not in b.
Hah! Well, at this stage my view would be this: I like this approach, I think it's pretty readable. I'm not sure why you check for typeof and then loose equality, rather than not just checking without type conversion initially (perhaps speed differences?), and I also don't like the variable names in array_diff, makes for a nasty read. Other than that, GJ! :)
@IanClark Because === is not good when used on objects. So I check for type first, obviously we can reject if they're different. If the type is the same, then handle comparison differently based on if that type is object or not.
0

Easy and simple way to achieve your goal

var a1 = [{"a":"A"},{"b":"B"}];
var a2 = [{"a":"A"},{"c":"C"},{"b":"B"}];

var max = (a1.length > a2.length) ? a1 : a2;
var min = (a1.length > a2.length) ? a2 : a1;
var newArray = [];

for ( var i = 0; i < max.length; i++ ) { // saving elements into string
    max[i] = JSON.stringify(max[i]);
    if ( typeof min[i] !== undefined ) {
        min[i] = JSON.stringify(min[i]);
    }
}

for ( var i = 0; i < max.length; i++ ) { // checking values uniqueness
    if ( min.indexOf(max[i]) === -1 ) {
        newArray.push(max[i]);
    }
}

// if you need new Array's elements back in object do following iteration
for ( var i in newArray ) { // loop recreate results array's elements into object again
    newArray[i] = JSON.parse(newArray[i]);
}

console.log(newArray); // result : [Object { c="C"}]

JSFiddle

6 Comments

This is exactly the way I was thinking about, however it won't work in case if the equal elements have different indices in the arrays. Otherwise theoretically these items won't be equal... or would... I don't know.
@VisioN It will work and you know why, because I've defined max and min variables, if some indices are missing it doesn't matter because max will hold array which has more elements in it even if there anything is missing
@VisioN And in the loop if some indices are missing won't impact the final result :)
@crypticous - I think he means this fails miserably
@crypticous - Noone said you should guess anything, but it fails if the arrays aren't exactly in the same order, so it doesn't really compare the objects, it compares the two arrays, and the slightest difference leads to failure.
|
0
var a1 = [{"a":"A"},{"b":"B"}];
var a2 = [{"a":"A"},{"b":"B"},{"c":"C"}];
var obj = {}, result = [];

function updateObjectCount(currentItem) {
    var keys, key;
    for (key in currentItem) {
        if (currentItem.hasOwnProperty(key)) {
            keys = key;
            break;
        }
    }
    obj[key] = obj[key] || {};
    obj[key][currentItem[key]] = (obj[key][currentItem[key]] || 0) + 1;
}

a1.forEach(updateObjectCount);
a2.forEach(updateObjectCount);

for (var key1 in obj) {
    if (obj.hasOwnProperty((key1))) {
        for (var key2 in obj[key1]) {
            if (obj.hasOwnProperty((key1))) {
                if (obj[key1][key2] === 1) {
                    var temp = {};
                    temp[key1] = key2;
                    result.push(temp)
                }
            }
        }
    }
}

console.log(result);
# [ { c: 'C' } ]

Comments

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.