【问题标题】:How to use underscore's "intersection" on objects?如何在对象上使用下划线的“交集”?
【发布时间】:2011-12-29 19:18:45
【问题描述】:
_.intersection([], [])

只适用于原始类型,对吧?

它不适用于对象。我怎样才能使它与对象一起工作(也许通过检查“Id”字段)?

var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ]
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ]

在这个例子中,结果应该是:

_.intersection(a, b);

[ {'id': 1, 'name': 'jake' } ];

【问题讨论】:

标签: javascript underscore.js


【解决方案1】:

您可以根据下划线的功能创建另一个功能。您只需从原始函数更改一行代码:

_.intersectionObjects = function(array) {
    var slice = Array.prototype.slice; // added this line as a utility
    var rest = slice.call(arguments, 1);
    return _.filter(_.uniq(array), function(item) {
      return _.every(rest, function(other) {
        //return _.indexOf(other, item) >= 0;
        return _.any(other, function(element) { return _.isEqual(element, item); });
      });
    });
  };

在这种情况下,您现在将使用下划线的 isEqual() 方法而不是 JavaScript 的相等比较器。我用你的例子试过了,它奏效了。以下是下划线关于 isEqual 函数的文档摘录:

_.isEqual(object, other) 
Performs an optimized deep comparison between the two objects, to determine if they should be considered equal.

您可以在此处找到文档:http://documentcloud.github.com/underscore/#isEqual

我把代码放在jsFiddle上,方便大家测试确认:http://jsfiddle.net/luisperezphd/jrJxT/

【讨论】:

  • 这是 3 个循环,所以基本上 O(n^3) 肯定你可以做得更好。 (尝试 O(nlogn)
  • 好点 Raynos,我试图尽可能多地使用已经存在的东西,这只是他提到使用的下划线现有 intersect() 函数的一小部分,因此我认为他是谁的表现满意。
  • Raynos,我在下面提供了一种替代算法,您可能更喜欢它。
  • 我用过这个,效果很好(对我来说足够快)。将 _.isEqual 更改为基于 Id 的相等是加速的最简单方法。
  • 等一下,两个哈希集的交集不是 O(|smaller set|) 吗?像,线性,而不是 lin-log?更不用说三次或二次了......编辑:哦,我想我正在考虑在对象的键集上获取交集的操作。
【解决方案2】:

这是一种替代算法,它应该灵活且性能更好。其中一项改进是您可以指定自己的比较函数,因此在您的情况下,您可以只比较 id(如果它是唯一标识符)。

function intersectionObjects2(a, b, areEqualFunction) {
    var results = [];

    for(var i = 0; i < a.length; i++) {
        var aElement = a[i];
        var existsInB = _.any(b, function(bElement) { return areEqualFunction(bElement, aElement); });

        if(existsInB) {
            results.push(aElement);
        }
    }

    return results;
}

function intersectionObjects() {
    var results = arguments[0];
    var lastArgument = arguments[arguments.length - 1];
    var arrayCount = arguments.length;
    var areEqualFunction = _.isEqual;

    if(typeof lastArgument === "function") {
        areEqualFunction = lastArgument;
        arrayCount--;
    }

    for(var i = 1; i < arrayCount ; i++) {
        var array = arguments[i];
        results = intersectionObjects2(results, array, areEqualFunction);
        if(results.length === 0) break;
    }

    return results;
}

你可以这样使用它:

var a = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'} ];
var b = [ { id: 1, name: 'jake' }, { id: 9, name: 'nick'} ];
var c = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'}, { id: 9, name: 'nick'} ];

var result = intersectionObjects(a, b, c, function(item1, item2) {
    return item1.id === item2.id;
});

或者你可以省略这个函数,它会使用下划线 _.isEqual() 函数,像这样:

var result = intersectionObjects(a, b, c);

您可以在此处的 jsFiddle 上找到它:http://jsfiddle.net/luisperezphd/43vksdn6/

【讨论】:

  • 谢谢,这对我目前的项目帮助很大。
  • 最佳答案,因为它可以过滤角蛋白元素
  • 谢谢!我不明白为什么我们需要if(Results.length === 0) break; 部分。有人可以向我解释为什么需要这样做吗?
  • @AlexandreBourlier 函数intersectionObjects() 接受多个数组。它返回传递给它的每个数组中存在的任何对象。当它发现一个对象在其中一个数组中不存在时,它不会费心检查其余的数组。这就是它破裂的原因。
  • 当然……这很明显。我不知道为什么我没有看到它。谢谢你的解释
【解决方案3】:

下划线中的数组方法非常强大,你应该只需要几行就可以完成你想做的事情:

var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];

var result = _(a).chain().map(function(ea) {
    return _.find(b, function(eb) {return ea.id == eb.id;});
}).compact().value();

如果您有大型数组,您可以通过添加一行来摆脱 compact() 调用:

var result = [];
_.each(a, function(ea) {
    var entry = _.find(b, function(eb) {return ea.id == eb.id;});
    if (entry) result.push(entry);
});

【讨论】:

  • 时间是二次的,这很糟糕
  • 是的,它是 O(N * M)。根据您的数据集的大小(例如几百个项目),这很可能根本不是问题。如果是,您可能需要一种完全不同的方法:使用排序数组,创建某种索引...
  • 在大多数情况下你是对的,但你可能会错过数据增长和性能下降的点,其他事情是如果你在大多数地方都有这样的代码,假设 100 个项目是 100 倍速度较慢,对于大型应用程序,它可能会变得很明显。但当然,每个案例都应该单独研究。
【解决方案4】:

我想分享我针对这些情况的一般解决方案。

我添加了一个通用函数来下划线,使用 mixin,它根据给定的 Hash 函数对两个集合执行二进制“数组”操作:

_.mixin({
    collectionOperation: function(arr1, arr2, hash, action) {
        var iArr1 = _(arr1).indexBy(hash)
            , iArr2 = _(arr2).indexBy(hash);
        return action(_(iArr1).keys(), _(iArr2).keys()).map(function (id) {
            return iArr1[id] || iArr2[id];
        });
    }
});

使用示例:

_([{id:1,v:'q'},{id:2,v:'p'}]).collectionOperation([{id:3,v:'pq'}], 'id', _.union )

请注意,'id' 可以替换为函数。

我相信这个解决方案是 O(n+m)。

【讨论】:

【解决方案5】:

在 lodash 4.0.0.我们可以这样尝试

var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];

_.intersectionBy(a, b, 'id');

输出:

[ {'id': 1, 'name': 'jake' } ];

【讨论】:

  • 是的,这些天使用最新的 lodash 轻松多了!
  • 终于要从下划线切换到lodash
【解决方案6】:

从技术上讲,它确实适用于对象,但您需要注意引用相等性。

var jake = {'id': 1, 'name': 'jake' },
    jenny = {'id':4, 'name': 'jenny'},
    nick =  {'id': 9, 'name': 'nick'};
var a = [jake, jenny]
var b = [jake, nick];

_.intersection(a, b);
// is
[jake]

【讨论】:

    【解决方案7】:
    var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
    var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];
    

    工作功能:

     function intersection(a,b){
      var c=[];
       for(m in a){
          for(n in b){
             if((a[m].id==a[n].id)&&(a[m].name==b[n].name))
                     c.push(a[m]);          
          }}
        return c;
      }
    console.log(intersection(a,b));
    

    在 Pointy 的建议下,我还专门尝试了 jQuery 中的代码。比较必须根据 JSON 对象的结构进行自定义。

    <script type="text/javascript">
    jQuery(document).ready(function(){
        var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
        var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];
        var c=[];
        jQuery.each(a, function(ka,va) {
           jQuery.each(b, function(kb,vb) {      
                    if(compare(va,vb))
                        c.push(va); 
         });   
        });
         console.log(c);  
    });
    function compare(a,b){
      if(a.id==b.id&&a.name==b.name)
         return true;
      else return false;
    }
    </script>
    

    【讨论】:

    • @Pointy。那么可以做些什么呢?在这里,for..in 迭代对象中的所有元素。这里的基本目标是将所有子对象与另一个子对象进行比较。
    • 当数组实例被传递时,代码可以遍历数字索引。 “for ... in”的问题在于它可能会混淆,因为它会包含 Array 原型对象上可能存在的属性。
    • 嗯,是的,使用“.each()”也是一个好主意:-)
    【解决方案8】:

    如果您只想比较对象:

    b = {"1":{"prod":"fibaro"},"2":{"prod":"aeotec"},"3":{"prod":"sw"}}; 
    a = {"1":{"prod":"fibaro"}};
    
    
    _.intersectObjects = function(a,b){
        var m = Object.keys(a).length;
        var n = Object.keys(b).length;
        var output;
        if (m > n) output = _.clone(a); else output = _.clone(b);
    
        var keys = _.xor(_.keys(a),_.keys(b));
        for(k in keys){
            console.log(k);
            delete output[keys[k]];
        }
        return output;
    }
    _.intersectObjects(a,b); // this returns { '1': { prod: 'fibaro' } }
    

    【讨论】:

      【解决方案9】:
      //nested array is in the format of [[],[],[]]
      
      function objectArrayIntersection(nestedArrays){     
      
          let intersectingItems = [];                
          let uniqArr = _.uniq(_.flatten(nestedArrays)); //intersecting items removed    
          const countOfNestedArrays = nestedArrays.length;
      
      
          for (let index = 0; index < uniqArr.length; index++) {
              let uniqItem = uniqArr[index];
              let foundCount = 0;
      
              for(var j = 0;j<countOfNestedArrays;j++){
                  var i = _.indexOf(nestedArrays[j],uniqItem);
                  if(i != -1)
                      foundCount ++;
              }
      
              if(foundCount ==  countOfNestedArrays){
                  intersectingItems.push(uniqItem);
              }
          }
      
          return intersectingItems;
      }
      

      我试过这样解决。

      【讨论】:

      • 首先对数组进行排序实际上有很大帮助;因为 _.uniq & _.indexOf 将在数组排序后进行二进制搜索。
      【解决方案10】:
      var a = {a:'a1',b:'b1'},
          b = {a:'a2',b:'b2',c:'c2'};
      
      _.pick(a,_.intersection(_.keys(a),_.keys(b)));
      
      // {a:'a1',b:'b1'}
      

      【讨论】:

      • 这是错误的,只对键进行比较:_.keys(a) 返回 ["a", "b"] 和 _.keys(b) 返回 ["a", "b", "c"].
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-19
      • 2014-01-26
      • 2012-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-26
      相关资源
      最近更新 更多