4

What is the best/most efficient way to find the common/different properties of an array of objects.

I need to identify the set of properties that exists in all objects and all have the same value(the common). Preferably I would also like to get an array with all other properties (the diff).

I have searched for an efficient library/function that can do it. But didn't find anything. So I tried on my own.

Consider this array of JS objects:

var objects = [{
    id: '2j2w4f',
    color: 'red',
    height: 20,
    width: 40,
    owner: 'bob'
}, {
    id: '2j2w3f',
    color: 'red',
    height: 20,
    width: 41,
    owner: 'bob'
}, {
    id: '2j2w2f',
    color: 'red',
    height: 21,
}, {
    id: '2j2w1f',
    color: 'red',
    height: 21,
    width: 44
}];

I would like to identify color (with value red) as the only common property. Note that they do not have the same set of properties. E.g. owner is not a common property.

This is my own attempt to solve it (using lodash):

function commonDifferentProperties(objects) {
    // initialize common as first object, and then remove non-common properties.
    var common = objects[0];
    var different = [];
    var i, obj;

    // iterate through the rest (note: i === 0 is not supposed to be covered by this)
    for (i = objects.length - 1; i > 0; i--) {
        obj = objects[i];
        // compare each property of obj with current common
        _.forOwn(obj, function (value, key) {
            // if property not in current common it must be different
            if (_.isUndefined(common[key])) {
                if (!_.contains(different, key)) {
                    different.push(key);
                }
            } else if (common[key] !== value) { // remove property from common if value is not the same
                delete common[key];
                different.push(key);
            }
        });

        // check that all properties of common is present in obj, if not, remove from common.
        _.forOwn(common, function (value, key) {
            if (_.isUndefined(obj[key])) {
                delete common[key];
                different.push(key);
            }
        });

    }

    return {
        common: common,
        different: different
    };
}

jsFiddle with the example

I have also tried a mapReduce approach, but that seemed even worse.

I still think this seems a bit complex/time consuming, and I will do this on 1000-10000 objects or more with 20-50 properties each.

Any suggestions?

7
  • Do you actually need the different properties too as in your fiddle? BTW: fixed your fiddle Commented Apr 21, 2014 at 14:27
  • Not familiar with lodash, but it looks to me like you are iterating over the properties of each object and comparing them to the first object, which seems backwards. You should iterate over the properties of only the first object and compare them with each other object (assuming you only want the common properties). Commented Apr 21, 2014 at 14:30
  • 4
    You're also corrupting your objects[0] by calling delete common[key];. Commented Apr 21, 2014 at 14:33
  • Instead of deleting from the first object, I would start with an empty object and add common properties. It will also avoid the problem mentioned by @LeonidBeschastny Commented Apr 21, 2014 at 14:52
  • @MattBurland Yes, I need the different one once somewhat. But I could also just check against the common ones, once I need to know if a given property is not-common. As I see it, you approach only works if they all have the same properties with different values. Lodash is a utility library Commented Apr 21, 2014 at 16:25

4 Answers 4

1

There are two things that are look wrong in your solution:

  • By var common = objects[0]; you don't copy the object, so you're going to corrupt the objects
  • You both check that all properties of common is present in obj, but also compare each property of obj with current common. That seems to be once too much. Didn't realize at first that you needed the different properties as well.

I'd loop over the data in two passes. In the first, you collect all apparent properties in one object, in the second you test whether they're common or not:

function commonDifferentProperties(objects) {
    var common = _.reduce(objects, function(acc, obj) {
        for (var p in obj)
            acc[p] = obj[p];
        return acc;
    }, {});
    var different = _.reduce(objects, function(acc, obj) {
        for (var p in common)
            if (common[p] !== obj[p]) {
                delete common[p];
                acc.push(p);
            }
        return acc;
    }, []);
    return {
        common: common,
        different: different
    };
}
Sign up to request clarification or add additional context in comments.

4 Comments

The reversed comparison is needed. If only obj is checked against common, then any properties that are in common that are not in any other obj will remain.
I'd have said the comparison of all obj properties against common is the reversed one… You only need it to compute the different properties
Does it matter which one we call reversed if both directions are required?
@Bergi I really like this approach, it is pretty straight forward and easy to follow. But yes both directions are required, and the name doesn't really matter :)
1

Here's what I did using just vanilla JS:

function commonDifferentProperties(objects) {
    var common = JSON.parse(JSON.stringify(objects[0]));
    var unmatchedProps = {};
    for (var i = 1; i < objects.length; i++) {
        for (var prop in objects[i]) {
            checkProps(objects[i],common,prop);
        }
        for (var commProp in common) {
            checkProps(common,objects[i],commProp);
        }
    }
    console.log(common);            // this is all the matched key/value pairs
    console.log(unmatchedProps);    // this is all the unmatched keys

    return { common: common, different: unmatchedProps };

    function checkProps(source, target, prop) {
        if (source.hasOwnProperty(prop)) {
            var val = source[prop];
            if (!target.hasOwnProperty(prop) || target[prop] !== val) {
                unmatchedProps[prop] = true;     // note: you could extend this to store values, or number of times you found this key, or whatever
                delete common[prop];
            }
        }
    }
}

http://jsfiddle.net/TwbPA/

So I copy the first object and use that to keep track of keys and values that are common. Then I iterate through all the other objects in your array and first look through all the key/values in the common object and compare to the current, deleting any missing properties from the common object if they are not in the current one, then I do the reverse to catch any properties in the current object that aren't in the common (or are in the current, but have the wrong value).

Comments

0

Edit

Sorry, i was in a hurry and didn't had enough time to think about it. Indeed, there's no need for a sort. I was thinking using a binary algorithm or something..

Here, the updated code without the sort. Console.time() gave me '3ms'. I'm doing similar to Bergi's solution, but instead of collecting all apparant properties i search for the element that has the fewest amount of properties. This reduces the amount of iterations on the second loop.

I've based the code on the following:

  • if object X has a property the selected object doesn't have, then it isn't a common property!
  • Thus the selected object has all common properties + extra's.
  • The selected object has the fewest properties, thus the iterations of validating is less.

http://jsfiddle.net/kychan/cF3ne/1/

//    returns the common properties of given array.
function getCommonProps(objects)
{
    //    storage var for object with lowest properties.
    var lowest = {obj:null, nProperties:1000};

    //    search for the object with lowest properties. O(n).
    for (var j in objects)
    {
        var _nProp = Object.keys(objects[j]).length;

        if (_nProp < lowest.nProperties)
            lowest = {obj:objects[j], nProperties:_nProp};
    }

    //    var that holds the common properties.
    var retArr = [];

    //    The object with the fewest properties should contain common properties.
    for (var i in lowest.obj)
        if (isCommonProp(objects, i))    retArr.push(i);

    return retArr;
}

//    Checks if the prop exists in all objects of given array.
function isCommonProp(arr, prop)
{
    for (var i in arr)
    {
        if (arr[i][prop]===undefined)
            return false;
    }

    return true;
}

console.time('getCommonProps()_perf');
console.log(getCommonProps(objects));
console.timeEnd('getCommonProps()_perf');

2 Comments

@Kai, after some thinking I see what you are trying to do. I don't think sort is necessary, you just need to find the "smallest" objects. (which is O(n)) Then you can for each property of smallest check is all other have the same property, and with the same value. This approach is faster, but I don't get the array with all other properties.
I assume this is the shame @MattBurland suggested above.
0

Here's another approach that uses reduce() and transform():

_.reduce(objects, function(result, item) { 
    if (_.isEmpty(result)) {
        return _.assign({}, item);
    }

    return _.transform(item, function(common, value, key) {
        if (result[key] === value) {
            common[key] = value;
        }
    }, {});
}, {});

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.