【问题标题】:Difference and intersection of two arrays containing objects包含对象的两个数组的差和交集
【发布时间】:2016-01-26 04:29:38
【问题描述】:

我有两个数组list1list2,它们的对象具有一些属性; userId 是 Id 或唯一属性:

list1 = [
    { userId: 1234, userName: 'XYZ'  }, 
    { userId: 1235, userName: 'ABC'  }, 
    { userId: 1236, userName: 'IJKL' },
    { userId: 1237, userName: 'WXYZ' }, 
    { userId: 1238, userName: 'LMNO' }
]

list2 = [
    { userId: 1235, userName: 'ABC'  },  
    { userId: 1236, userName: 'IJKL' },
    { userId: 1252, userName: 'AAAA' }
]

我正在寻找一种简单的方法来执行以下三个操作:

  1. list1 operation list2 应该返回元素的交集:

    [
        { userId: 1235, userName: 'ABC'  },
        { userId: 1236, userName: 'IJKL' }
    ]
    
  2. list1 operation list2 应该返回 list1 中没有出现在 list2 中的所有元素的列表:

    [
        { userId: 1234, userName: 'XYZ'  },
        { userId: 1237, userName: 'WXYZ' }, 
        { userId: 1238, userName: 'LMNO' }
    ]
    
  3. list2 operation list1 应该返回 list2 中不会出现在 list1 中的元素列表:

    [
        { userId: 1252, userName: 'AAAA' }
    ]
    

【问题讨论】:

  • 您的标题说“差异”,但您的问题提到了“交叉点”。它是哪一个?你能举例说明你期望的输出是什么吗?

标签: javascript arrays set-intersection set-difference set-operations


【解决方案1】:

您可以定义三个函数inBothinFirstOnlyinSecondOnly,它们都将两个列表作为参数,并返回一个从函数名称可以理解的列表。主要逻辑可以放在三个都依赖的公共函数operation中。

这里有几个实现供 operation 选择,您可以在下面找到一个 sn-p:

  • 普通的旧 JavaScript for 循环
  • 使用 filtersome 数组方法的箭头函数
  • 使用Set 优化查找

普通的旧 for 循环

// Generic helper function that can be used for the three operations:        
function operation(list1, list2, isUnion) {
    var result = [];
    
    for (var i = 0; i < list1.length; i++) {
        var item1 = list1[i],
            found = false;
        for (var j = 0; j < list2.length && !found; j++) {
            found = item1.userId === list2[j].userId;
        }
        if (found === !!isUnion) { // isUnion is coerced to boolean
            result.push(item1);
        }
    }
    return result;
}

// Following functions are to be used:
function inBoth(list1, list2) {
    return operation(list1, list2, true);
}

function inFirstOnly(list1, list2) {
    return operation(list1, list2);
}

function inSecondOnly(list1, list2) {
    return inFirstOnly(list2, list1);
}

// Sample data
var list1 = [
    { userId: 1234, userName: 'XYZ'  }, 
    { userId: 1235, userName: 'ABC'  }, 
    { userId: 1236, userName: 'IJKL' },
    { userId: 1237, userName: 'WXYZ' }, 
    { userId: 1238, userName: 'LMNO' }
];
var list2 = [
    { userId: 1235, userName: 'ABC'  },  
    { userId: 1236, userName: 'IJKL' },
    { userId: 1252, userName: 'AAAA' }
];
  
console.log('inBoth:', inBoth(list1, list2)); 
console.log('inFirstOnly:', inFirstOnly(list1, list2)); 
console.log('inSecondOnly:', inSecondOnly(list1, list2)); 

使用filtersome 数组方法的箭头函数

这使用了一些 ES5 和 ES6 特性:

// Generic helper function that can be used for the three operations:        
const operation = (list1, list2, isUnion = false) =>
    list1.filter( a => isUnion === list2.some( b => a.userId === b.userId ) );

// Following functions are to be used:
const inBoth = (list1, list2) => operation(list1, list2, true),
      inFirstOnly = operation,
      inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);

// Sample data
const list1 = [
    { userId: 1234, userName: 'XYZ'  }, 
    { userId: 1235, userName: 'ABC'  }, 
    { userId: 1236, userName: 'IJKL' },
    { userId: 1237, userName: 'WXYZ' }, 
    { userId: 1238, userName: 'LMNO' }
];
const list2 = [
    { userId: 1235, userName: 'ABC'  },  
    { userId: 1236, userName: 'IJKL' },
    { userId: 1252, userName: 'AAAA' }
];
  
console.log('inBoth:', inBoth(list1, list2)); 
console.log('inFirstOnly:', inFirstOnly(list1, list2)); 
console.log('inSecondOnly:', inSecondOnly(list1, list2));

优化查找

由于嵌套循环,上述解决方案具有 O(n²) 时间复杂度——some 也代表一个循环。因此,对于大型数组,您最好在用户 ID 上创建一个(临时)散列。这可以通过提供Set (ES6) 作为将生成过滤器回调函数的函数的参数on-the-fly 完成。然后,该函数可以使用has 在恒定时间内执行查找:

// Generic helper function that can be used for the three operations:        
const operation = (list1, list2, isUnion = false) =>
    list1.filter(
        (set => a => isUnion === set.has(a.userId))(new Set(list2.map(b => b.userId)))
    );

// Following functions are to be used:
const inBoth = (list1, list2) => operation(list1, list2, true),
      inFirstOnly = operation,
      inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);

// Sample data
const list1 = [
    { userId: 1234, userName: 'XYZ'  }, 
    { userId: 1235, userName: 'ABC'  }, 
    { userId: 1236, userName: 'IJKL' },
    { userId: 1237, userName: 'WXYZ' }, 
    { userId: 1238, userName: 'LMNO' }
];
const list2 = [
    { userId: 1235, userName: 'ABC'  },  
    { userId: 1236, userName: 'IJKL' },
    { userId: 1252, userName: 'AAAA' }
];
  
console.log('inBoth:', inBoth(list1, list2)); 
console.log('inFirstOnly:', inFirstOnly(list1, list2)); 
console.log('inSecondOnly:', inSecondOnly(list1, list2));

【讨论】:

    【解决方案2】:

    简短回答:

    list1.filter(a => list2.some(b => a.userId === b.userId));  
    list1.filter(a => !list2.some(b => a.userId === b.userId));  
    list2.filter(a => !list1.some(b => a.userId === b.userId));  
    

    更长的答案:
    上面的代码将通过userId 值检查对象,
    如果需要复杂的比较规则,可以自定义比较器:

    comparator = function (a, b) {
        return a.userId === b.userId && a.userName === b.userName
    };  
    list1.filter(a => list2.some(b => comparator(a, b)));
    list1.filter(a => !list2.some(b => comparator(a, b)));
    list2.filter(a => !list1.some(b => comparator(a, b)));
    

    还有一种方法可以通过引用比较对象
    警告!具有相同值的两个对象将被视为不同:

    o1 = {"userId":1};
    o2 = {"userId":2};
    o1_copy = {"userId":1};
    o1_ref = o1;
    [o1].filter(a => [o2].includes(a)).length; // 0
    [o1].filter(a => [o1_copy].includes(a)).length; // 0
    [o1].filter(a => [o1_ref].includes(a)).length; // 1
    

    【讨论】:

    • 太棒了!又好又干净!
    【解决方案3】:

    只要使用JS的filtersome数组方法就可以了。

    let arr1 = list1.filter(e => {
       return !list2.some(item => item.userId === e.userId);
    });
    

    这将返回list1 中存在但list2 中不存在的项目。如果您正在寻找两个列表中的共同项目。就这样做吧。

    let arr1 = list1.filter(e => {
       return list2.some(item => item.userId === e.userId); // take the ! out and you're done
    });
    

    【讨论】:

      【解决方案4】:

      使用lodash's_.isEqual方法。具体来说:

      list1.reduce(function(prev, curr){
        !list2.some(function(obj){
          return _.isEqual(obj, curr)
        }) ? prev.push(curr): false;
        return prev
      }, []);
      

      上面给出了 A given !B 的等价物(在 SQL 术语中,A LEFT OUTER JOIN B)。您可以在代码周围移动代码以获得您想要的!

      【讨论】:

        【解决方案5】:
        function intersect(first, second) {
            return intersectInternal(first, second, function(e){ return e });
        }
        
        function unintersect(first, second){
            return intersectInternal(first, second, function(e){ return !e });  
        }
        
        function intersectInternal(first, second, filter) {
            var map = {};
        
            first.forEach(function(user) { map[user.userId] = user; });
        
            return second.filter(function(user){ return filter(map[user.userId]); })
        }
        

        【讨论】:

          【解决方案6】:

          这是一个 函数式编程 解决方案,带有下划线/lodash 来回答您的第一个问题(交叉点)。

          list1 = [ {userId:1234,userName:'XYZ'}, 
                    {userId:1235,userName:'ABC'}, 
                    {userId:1236,userName:'IJKL'},
                    {userId:1237,userName:'WXYZ'}, 
                    {userId:1238,userName:'LMNO'}
                  ];
          
          list2 = [ {userId:1235,userName:'ABC'},  
                    {userId:1236,userName:'IJKL'},
                    {userId:1252,userName:'AAAA'}
                  ];
          
          _.reduce(list1, function (memo, item) {
                  var same = _.findWhere(list2, item);
                  if (same && _.keys(same).length === _.keys(item).length) {
                      memo.push(item);
                  }
                  return memo
              }, []);
          

          我会让你改进这个来回答其他问题;-)

          【讨论】:

          • 注意:在真正的函数式编程风格中,不会有 push(它会改变数组)
          【解决方案7】:

          这是对我有用的解决方案。

           var intersect = function (arr1, arr2) {
                      var intersect = [];
                      _.each(arr1, function (a) {
                          _.each(arr2, function (b) {
                              if (compare(a, b))
                                  intersect.push(a);
                          });
                      });
          
                      return intersect;
                  };
          
           var unintersect = function (arr1, arr2) {
                      var unintersect = [];
                      _.each(arr1, function (a) {
                          var found = false;
                          _.each(arr2, function (b) {
                              if (compare(a, b)) {
                                  found = true;    
                              }
                          });
          
                          if (!found) {
                              unintersect.push(a);
                          }
                      });
          
                      return unintersect;
                  };
          
                  function compare(a, b) {
                      if (a.userId === b.userId)
                          return true;
                      else return false;
                  }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-08
            • 2019-09-28
            • 1970-01-01
            • 2019-05-20
            • 1970-01-01
            相关资源
            最近更新 更多