【问题标题】:Best way to get intersection of keys of two objects?获得两个对象的键交集的最佳方法?
【发布时间】:2016-03-27 08:29:47
【问题描述】:

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

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

我显然可以做一个循环,看看另一个对象中是否存在同名的键,但我想知道这对于某些函数式编程和映射/过滤/减少使用是否是一个很好的例子?我自己没有做过那么多函数式编程,但我有一种感觉,对于这个问题,可以有一个干净而聪明的解决方案。

【问题讨论】:

  • Lodash 有 intersection 作为一种方法,如果你还不知道的话。
  • @Xotic750 似乎只适用于数组?话又说回来,可能有很多像Object.keys 这样的方法来获取作为数组的键。
  • 当然,你需要每个对象的键,就像下面的答案一样,Object.keys 或 lodash 有_.keys_.intersection(_.keys(firstObject), _.keys(secondObject));

标签: javascript object functional-programming big-o intersection


【解决方案1】:

我建议的程序是:

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

【讨论】:

  • 我可能也在想同样的问题,就像你在任何情况下使用k1.filterk2.filter 一样重要吗?
  • @HunanRostomyan 我不相信它永远不会是假的,因为过滤器回调函数中的第一个值返回被过滤的数组的当前值。在这种情况下,k1 正在被过滤,使第一次检查变得多余,因为我们知道该值在通过回调传递时必须存在。
  • @Piwwoli 没关系,(A和B)和(B和A)的交集是同一个集合,所以可以从A的每个成员开始,检查是否也在B中, or 从 B 的每个成员开始并检查它是否也在 A 中。我的问题是您不需要检查两个成员资格,因为元素 (x ) 已经取自其中一组(@Monica 刚刚解释了同样的事情)。
  • @HunanRostomyan 是的,更新 :) 谢谢。
  • 正如 Nina 的回答中提到的那样,所做的更改是否使此解决方案优于 O(N^3)
【解决方案2】:

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

【讨论】:

  • @AmitKriplani 在 ES5 中根本不保证,并保证在 ES2015 中按创建顺序
  • @NinaScholz:这是一个很好的答案。如果reduce 是纯的(没有使用免费的result)会更漂亮 PS:OP 不关心值相等 - 它只是键名的交集
  • @zerkms,现在好点了吗?
  • @zerkms 大概 NlogN 用于排序(不是吗?)。剩下的,reduce 部分增加的复杂性呢?
  • 第二次尝试做了一些不同的事情;可能想.hasOwnProperty 检查。
【解决方案3】:

给出的答案很好而且令人惊讶,但voidanswer 可能存在问题,那就是: "如果有意将属性值之一设置为 undefined"

Ninaanswer 很好(真的很棒),但我们正处于有趣的 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 提到了 cmets 中的一些性能问题,这是合理的,因此下面的代码似乎是解决问题的更好方法:

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

【讨论】:

  • 此外,这里没有人关心对象的长度。如果对象“a”的长度为 1000,对象“b”为 10,如果设置了 10 索引,则过滤效果不好在 1000 上比在 10 中设置的检查 1000 ;)..
  • 很好 - 这也是我使用这个基准测试最快的:jsben.ch/6dB51
【解决方案4】:

递归函数

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

在这种情况下,函数拦截两个对象中都存在的属性,并分配“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));
    }

【讨论】:

    【解决方案5】:

    这是一个简单的入口,非常实用,处理任意数量的对象,并从传递的第一个对象返回匹配键的值。

    这种行为类似于 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)));
    }
    

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 对键数组进行预排序的非常有趣且实用的想法!
      猜你喜欢
      • 2011-09-10
      • 2017-08-28
      • 2012-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多