JNevens
JNevens

Reputation: 11982

Filter multiple arrays of objects

I have the following situation:

There are an undefined number of arrays. Each array contains some objects. Each of these objects has an id element, among some other elements.

//Example objects:

f = [{'id': 1, 'name': 'a'}, {'id': 2, 'name': 'b'},
     {'id': 3, 'name': 'c'}, {'id': 1, 'name': 'd'}, ...]
g = [{'id': 2, 'name': 'e'}, {'id': 4, 'name': 'f'}, 
     {'id': 3, 'name': 'g'}, {'id': 4, 'name': 'h'}, ...]
h = ...
i = ...

I would like to filter out all the duplicates, not only inside every separate array, but also across the different arrays.

Edit: Two objects are a duplicate when their id element are the same. Precedence does not matter, first occurrence is fine for this.

I came up with the following function:

function uniqueObjects() {
    var seen = [];
    for (var i=0; i<arguments.length; i++) {
        var arg = arguments[i];
        for (var j=0; j<arg.length; j++) {
            var elm = arg[j];
            var key = elm['id'];
            if (seen.indexOf(key) > -1) {
                arg.pop(elm);
            } else {
                seen.push(key);
            }
        }
    }
    return arguments;
}

But this does not seem to work properly, since I got the following values:

{'0': [{'id': 1, 'name': 'a'}, 
       {'id': 2, 'name': 'b'}, 
       {'id': 3, 'name': 'c'}], 
 '1': [{'id': 2, 'name': 'e'}, 
       {'id': 4, 'name': 'f'}]}

Upvotes: 0

Views: 1098

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074285

The main problem is here:

arg.pop(elm);

pop removes the last element in the array and doesn't take any arguments. You may have meant:

arg.splice(j, 1);

...but then you need to not increment j if you do that. So that gives us:

function uniqueObjects() {
    var seen = [];
    for (var i=0; i<arguments.length; i++) {
        var arg = arguments[i];
        var j = 0;                       // **
        while (j<arg.length) {           // ** Made this a while loop
            var elm = arg[j];
            var key = elm['id'];
            if (seen.indexOf(key) > -1) {
                arg.splice(j, 1);         // ** Remove this entry
            } else {
                seen.push(key);
                ++j;                      // ** Increment j
            }
        }
    }
    return arguments;
}

However, there is a much more efficient way to track which id values you've seen: Using an object:

function uniqueObjects() {
    var seen = {};
    for (var i=0; i<arguments.length; i++) {
        var arg = arguments[i];
        var j = 0;
        while (j<arg.length) {
            var elm = arg[j];
            var key = " " + elm['id'];    // ** Note the space
            if (seen[key]) {              // ** Updated check
                arg.splice(j, 1);
            } else {
                seen[key] = true;         // ** Set the flag
                ++j;
            }
        }
    }
    return arguments;
}

Why put the space before the id when creating key? On the off-chance that you have id values with names that exist on objects, like toString or valueOf. (ES6 will introduce Map objects and we won't have to do that anymore.)

I'd also recommend not returning the pseudo-array arguments, but instead using a real array. So:

function uniqueObjects() {
    var seen = {};
    var args = Array.prototype.slice.call(arguments, 0); // <== Make real array
    for (var i=0; i<args.length; i++) {
        var arg = args[i];
        var j = 0;
        while (j<arg.length) {
            var elm = arg[j];
            var key = " " + elm['id'];    // ** Note the space
            if (seen[key]) {              // ** Updated check
                arg.splice(j, 1);
            } else {
                seen[key] = true;         // ** Set the flag
                ++j;
            }
        }
    }
    return args;
}

Live Example:

function uniqueObjects() {
    var seen = {};
    var args = Array.prototype.slice.call(arguments, 0);
    for (var i=0; i<args.length; i++) {
        var arg = args[i];
        var j = 0;
        while (j<arg.length) {
            var elm = arg[j];
            var key = " " + elm['id'];    // ** Note the space
            if (seen[key]) {              // ** Updated check
                arg.splice(j, 1);
            } else {
                seen[key] = true;         // ** Set the flag
                ++j;
            }
        }
    }
    return args;
}

var f = [
  {'id': 1, 'name': 'a'}, {'id': 2, 'name': 'b'},
  {'id': 3, 'name': 'c'}, {'id': 1, 'name': 'd'}
];
var g = [
  {'id': 2, 'name': 'e'}, {'id': 4, 'name': 'f'},
  {'id': 3, 'name': 'g'}, {'id': 4, 'name': 'h'}
];
snippet.log("Before: " + JSON.stringify([f, g]));
var result = uniqueObjects(f, g);
snippet.log("After: " + JSON.stringify(result));
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Upvotes: 2

Related Questions