获取两个对象键交集的最佳方法?

Best way to get intersection of keys of two objects?

我有两个像这样的对象文字:

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
}

我想得到这两个对象文字的键的交集,如下所示:

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

我显然可以做一个循环,看看另一个对象中是否存在同名的键,但我想知道这是否适合某些函数式编程和 map/filter/reduce 用法?我自己并没有做过那么多的函数式编程,但我有一种感觉,可能存在一个干净而聪明的解决方案来解决这个问题。

我建议的程序是:

  1. 为其中一个对象使用 Object.keys() 获取键的 array
  2. 使用 .filter 查找数组的交集,并检查第二个对象是否包含与第一个数组匹配的键。

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));

没有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>');

第二次尝试,时间复杂度为 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>');

给出的答案很好而且令人惊讶,但 void's 中可能存在问题,那就是: “如果有意将 属性 值之一设置为 undefined 会怎样?”

Nina's 很好(真的很棒)但是因为我们处在欢乐的时代JavaScript 我觉得我的不会太差:

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>');


更新

onalbi 在评论中提到了一些合理的性能问题,因此下面的代码似乎是处理该问题的更好方法:

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>');

递归函数

这是其他解决方案,也许对您有帮助。我使用递归函数来拦截两个对象。此解决方案的优点是您无需担心同时是对象的属性。

在这种情况下,函数拦截存在于两个对象中的属性并分配 'objSource' 的值,就像拦截属性的最终值一样。

{
        function interceptObjects(objSource, objInterface) {
            let newObj = {};
            for (const key in objSource) {
                if (objInterface.hasOwnProperty(key)) {
                    // in javascript an array is a object too.
                    if (objSource[key] instanceof Object && !Array.isArray(objSource[key]) && objInterface[key] instanceof Object && !Array.isArray(objInterface[key])) {
                        newObj[key] = {};
                        newObj[key] = interceptObjects(objSource[key], objInterface[key])
                    } else {
                        newObj[key] = objSource[key];
                    }

                }
            }
            return newObj;
        }
        
        
        // FOR TESTING


    let objSource = {
            attr1: '',
            attr2: 2,
            attr3: [],
            attr4: {
                attr41: 'lol',
                attr42: 12,
                attr43: 15,
                attr45: [1, 4],
            },
            attr5: [2, 3, 4],
        };


        let objInterface = {
            attr1: null,
            attr4: {
                attr41: null,
                attr42: 12,
                attr45: [1],
            },
            attr5: [],
            attr6: null,
        };


        console.log(this.interceptObjects(objSource, objInterface));
    }

这是一个简单的条目,非常实用,可以处理任意数量的对象,并且 returns 来自传递的第一个对象的匹配键的值。

此行为类似于 PHP 中 array_intersect_key() 的行为,以防有人搜索。

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]))));
}

为了更好的解释和评论,在此处展开​​

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)));
}

请务必注意,此解决方案仅适用于字符串键,符号键将被忽略,最终对象将不包含任何键。尽管也可以编写类似的函数来比较符号相交。

我知道这是一个旧的 post,但是,我想分享我今天写的一个解决方案,我认为它是高效和干净的。

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)));
}

这个函数可以接受n个对象,并找到相交键。

这是它的工作原理。

  1. 映射对象,创建键数组数组。
  2. 按长度对数组进行排序,这会将最小的键数组放在最前面。
  3. 最后,通过根据下一个列表过滤每个键列表来减少我们的键数组。

我认为这个算法的巧妙之处在于键数组的预排序。通过从最小的键列表开始,我们可以减少比较键的工作量。

这是用法:

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' ]