Best way to get intersection of keys of two objects?

33,937

Solution 1

A solution without indexOf.

var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
    secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };

function intersection(o1, o2) {
    return Object.keys(o1).concat(Object.keys(o2)).sort().reduce(function (r, a, i, aa) {
        if (i && aa[i - 1] === a) {
            r.push(a);
        }
        return r;
    }, []);
}

document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');

Second attempt with O(n).

var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
    secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };

function intersection(o1, o2) {
    return Object.keys(o1).filter({}.hasOwnProperty.bind(o2));
}

document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');

Solution 2

The given answers are nice and astonishing but there could be a problem in void's answer and that is: "What if one of property values intentionally set to undefined."

Nina's answer is good (really fantastic) but as we are in era of fun JavaScript I think mine wont be too bad:

var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30 }
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 }

function intersect(o1, o2){
    return Object.keys(o1).filter(k => k in o2)
}

document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');

Update

onalbi mentioned some performance issue in comments which is rational and therefore the code bellow seems to be a better way to handle the problem:

var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30};
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30};

function intersect(o1, o2) {

  const [k1, k2] = [Object.keys(o1), Object.keys(o2)];
  const [first, next] = k1.length > k2.length ? [k2, o1] : [k1, o2];
  return first.filter(k => k in next);
}

document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');

Solution 3

The procedure i will suggest is:

  1. Get the array of keys using Object.keys() for one of the objects.
  2. Find the intersection the array using .filter and checking if the second object contains a key matching the first array.

var firstObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  b: 20,
  e: 30
}

var secondObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  c: 20,
  d: 30
}

function getIntKeys(obj1, obj2){

    var k1 = Object.keys(obj1);
    return k1.filter(function(x){
        return obj2[x] !== undefined;
    });
  
}

alert(getIntKeys(firstObject, secondObject));

Solution 4

Here is a simple entry, very functional, handles any number of objects, and returns the values of the matching keys from the first object passed.

This behavior is similar to that of array_intersect_key() in PHP in case anyone is searching for that.

function intersectKeys(first, ...rest) {
    const restKeys = rest.map(o => Object.keys(o));
    return Object.fromEntries(Object.entries(first).filter(entry => restKeys.every(rk => rk.includes(entry[0]))));
}

Expanded here for better explanation and commenting

function intersectKeys(first, ...rest) {
    // extract the keys of the other objects first so that won't be done again for each check
    const restKeys = rest.map(o => Object.keys(o));
    // In my version I am returning the first objects values under the intersect keys
    return Object.fromEntries(
        // extract [key, value] sets for each key and filter them, Object.fromEntries() reverses this back into an object of the remaining fields after the filter
        Object.entries(first).filter(
            // make sure each of the other object key sets includes the current key, or filter it out
            entry => restKeys.every(
                rk => rk.includes(entry[0])
            )
        )
    );
    // to get JUST the keys as OP requested the second line would simplify down to this
    return Object.keys(first).filter(key => restKeys.every(rk => rk.includes(key)));
}

It's important to note that this solution only works on string keys, Symbol keys will be ignored and the final object will not contain any. Though a similar function could be written to compare Symbol intersect as well.

Solution 5

I know this is an old post, however, I want to share a solution I wrote today that I believe is efficient and clean.

function intersectingKeys(...objects) {
  return objects
    .map((object) => Object.keys(object))
    .sort((a, b) => a.length - b.length)
    .reduce((a, b) => a.filter((key) => b.includes(key)));
}

This function can take in n number of objects, and find the intersecting keys.

This is how it works.

  1. Map the objects, creating an array of key arrays.
  2. Sort the array by length, this puts the smallest key arrays first.
  3. Finally, reduce our key arrays, by filtering each list of keys against the next list.

I think the clever part of this algorithm is the pre sorting of the key arrays. By starting with the smallest list of keys, we have less work to do comparing keys.

Here is the usuage:

var firstObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  b: 20,
  e: 30,
};

var secondObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  c: 20,
  d: 30,
};

intersectingKeys(firstObject, secondObject);
// [ 'x', 'y', 'z', 'a' ]
Share:
33,937
Swiffy
Author by

Swiffy

Updated on October 14, 2020

Comments

  • Swiffy
    Swiffy over 3 years

    I have two object literals like so:

    var firstObject =
    {
        x: 0,
        y: 1,
        z: 2,
    
        a: 10,
        b: 20,
        e: 30
    }
    
    var secondObject =
    {
        x: 0,
        y: 1,
        z: 2,
    
        a: 10,
        c: 20,
        d: 30
    }
    

    I want to get the intersection of the keys these two object literals have like so:

    var intersectionKeys  = ['x', 'y', 'z', 'a']
    

    I can obviously do a loop and see if a key with the same name exists in the other object, but I am wondering if this would be a good case for some functional programming and map / filter / reduce usage? I myself have not done that much functional programming, but I have a feeling, that there could exist a clean and clever solution for this problem.

  • zerkms
    zerkms over 8 years
    @AmitKriplani not guaranteed at all in ES5 and guaranteed to be in creation order in ES2015
  • zerkms
    zerkms over 8 years
    @NinaScholz: that's a nice answer. It would be even prettier if the reduce was pure (did not use a free result) PS: OP does not care about the values equality - it's just an intersection of key names
  • Swiffy
    Swiffy over 8 years
    I was probably wondering the same, like does it matter if you do k1.filter or k2.filter in any case?
  • Nina Scholz
    Nina Scholz over 8 years
    @AmitKriplani, maybe, but with concat there is no sort.
  • Amit Kriplani
    Amit Kriplani over 8 years
    Hmmm, it is an array then
  • Swiffy
    Swiffy over 8 years
    Why would this answer be better / worse than the one submitted by void?
  • Nina Scholz
    Nina Scholz over 8 years
    @AmitKriplani, i you concat two sorted arrays, the the result is not sorted by default.
  • Monica Olejniczak
    Monica Olejniczak over 8 years
    @HunanRostomyan I don't believe it can ever be false, since the first value in the filter callback function returns the current value of the array being filtered. In this case, k1 is being filtered, making the first check redundant since we know the value has to exist as it is passed through the callback.
  • Nina Scholz
    Nina Scholz over 8 years
    @zerkms, is it better now?
  • Hunan Rostomyan
    Hunan Rostomyan over 8 years
    @Piwwoli Doesn't matter, the intersection of (A and B) and (B and A) is the same set, so you can begin with each member of A and check if it's also in B, or begin with each member of B and check if it's also in A. What I'm suggesting with my question, is that you don't need to check two memberships because the element (x) is already taken from one of those sets (@Monica just explained the same thing).
  • zerkms
    zerkms over 8 years
    @NinaScholz I think it's much better yep.
  • void
    void over 8 years
    @HunanRostomyan yes true, Update :) Thanks.
  • Swiffy
    Swiffy over 8 years
    Do the changes made make this solution better than O(N^3), as someone mentioned in Nina's answer?
  • void
    void over 8 years
    Dude it is O(N^2), and in this case with a cleaner code better then this complexity can not be achieved.
  • void
    void over 8 years
    @zerkms and this is definitely doable in O(nlog(n)) but the code will be super lengthy and unreadable.
  • void
    void over 8 years
    And concat then sorting and reducing are defintely more complex operations then what I am using, i dont understand a point of using them.
  • zerkms
    zerkms over 8 years
    @void this solution is O(N LogN)
  • Hunan Rostomyan
    Hunan Rostomyan over 8 years
    @zerkms Presumably the NlogN is for the sort (no?). What about the rest, the added complexity of the reduce part?
  • zerkms
    zerkms over 8 years
    @HunanRostomyan so, what is it if not O(N LogN)?
  • Hunan Rostomyan
    Hunan Rostomyan over 8 years
    @zerkms It's going to traverse the list at least once, so O(N logN + N), so O(N logN). I guess. I see.
  • Swiffy
    Swiffy over 8 years
    Although this solution is more complex than voids, I find speed over complexity better, so I am going to accept this answer.
  • Hunan Rostomyan
    Hunan Rostomyan over 8 years
    That second attempt does something a little different; might want to .hasOwnProperty check instead.
  • onalbi
    onalbi over 5 years
    Also here no body take care about the length of the object.. what happens if the object 'a' has length 1000 and object 'b' 10, is not better to filter if 10 index are set on 1000 than check 1000 are set in 10 ;)..
  • adiga
    adiga over 5 years
    This seems wrong. If any of the properties have undefined values, then this won't work. Set x:undefined in secondObject. It will exude x from the final result.
  • Swiffy
    Swiffy over 3 years
    Very interesting and functional idea that pre sorting of the key arrays!
  • Jannis Hell
    Jannis Hell over 3 years
    Nice - it's also the fastest for me using this benchmark: jsben.ch/6dB51