获取n个数组的交集

时间:2017-03-05 03:28:55

标签: javascript arrays intersection

使用ES6的Set,给定两个数组,我们可以像这样得到交集:

let a = new Set([1,2,3])
let b = new Set([1,2,4])
let intersect = new Set([...a].filter(i => b.has(i)));

我们如何获得n数组的交集?

更新

我正试图围绕这个用于以下用例。我有一个至少有一个元素的二维数组。

parts.forEach(part => {
  intersection = new Set()
})

如何获得parts中每个元素(数组)的交集?

5 个答案:

答案 0 :(得分:6)

假设你有一些可以交叉两个集合的函数function intersect(set1, set2) {...},你可以使用reduce得到一组集合的交集:

function intersect(a, b) {
    return new Set(a.filter(i => b.has(i)));
}

var sets = [new Set([1,2,3]), ...];
var intersection = sets.reduce(intersect);

答案 1 :(得分:3)

您可以使用intersectArray.filter().map()方法组合创建.every()辅助函数。

此答案的灵感来自Xufox上方的评论,他提到在Array#every谓词中使用filter

function intersect (first = [], ...rest) {
   rest = rest.map(array => new Set(array))
   return first.filter(e => rest.every(set => set.has(e)))
}

let parts = [
  [1, 2, 3],
  [1, 2, 4],
  [1, 5, 2]
]

console.log(
  intersect(...parts)
)

答案 2 :(得分:2)

ES6仍有while

这是一种功能类型,由于处理量过大,很容易造成长时间滞后。毫无疑问甚至优先使用ES6和数组方法(如reduce,filter等)比简单的老式循环(如while和for)更为真实。

当计算多个集合的交集时,如果发现某个项目不是交集的一部分,则每次迭代完成的工作量应该减少。因为forEach不能打破你被迫仍然迭代所有元素。添加一些代码以避免在发现当前项目不属于搜索时进行搜索可以提高性能,但这是一个真正的问题。

只是为了从数组,集合或地图中删除单个项目而创建全新数据集的倾向。这是一个非常糟糕的习惯,我越来越多地看到人们采用ES5的方式。

获取n组的交集。

所以对手头的问题。找到多组的交集。

解决方案B

典型的ES6解决方案

function intersectB(firstSet, ...sets) {
    // function to intercept two sets
    var intersect = (a,b) => {
        return new Set([...a].filter(item => b.has(item)))
    };

    // iterate all sets comparing the first set to each.
    sets.forEach(sItem => firstSet = intersect(firstSet, sItem));

    // return the result.
    return firstSet;
}

var sets = [new Set([1,2,3,4]), new Set([1,2,4,6,8]), new Set([1,3,4,6,8])];
var inter = intersectB(...sets);
console.log([...inter]);

效果很好,对于简单的测试用例,执行时间不到一毫秒。但是在我的书中,这是一个记忆效率低下的结果,创建数组,并且几乎在每一行都设置并在结果已知时迭代整个集合。

让我们再做一些工作。 100套,10个测试中最多10000个项目,每个测试具有不同数量的匹配项目。大多数拦截将返回空集。

警告会导致页面挂起一整秒...... :(

// Create a set of numbers from 0 and < count
// With a odds for any number occurring to be odds
// return as a new set;
function createLargeSet(count,odds){
    var numbers = new Set();
    while(count-- > 0){
        if(Math.random() < odds){
            numbers.add(count);
        }
    }
    return numbers;
}
// create a array of large sets
function bigArrayOfSets(setCount,setMaxSize,odds){
    var bigSets = [];
    for(var i = 0; i < setCount; i ++){
        bigSets.push(createLargeSet(setMaxSize,odds));
    }
    return bigSets;
}
function intersectB(firstSet, ...sets) {
    var intersect = (a,b) => {
        return new Set([...a].filter(item => b.has(item)))
    };
    sets.forEach(sItem => firstSet = intersect(firstSet, sItem));
    return firstSet;
}
var testSets = [];
for(var i = 0.1; i <= 1; i += 0.1){
    testSets.push(bigArrayOfSets(100,10000,i));
}

var now = performance.now();
testSets.forEach(testDat => intersectB(...testDat));
var time = performance.now() - now;
console.log("Execution time : " + time);

解决方案A

更好的方式,不是花哨而是更有效率。

function intersectA(firstSet,...sets) {
    var count = sets.length;
    var result = new Set(firstSet); // Only create one copy of the set
    firstSet.forEach(item => {
        var i = count;
        var allHave = true;
        while(i--){
            allHave = sets[i].has(item)
            if(!allHave) { break }  // loop only until item fails test
        }
        if(!allHave){
            result.delete(item);  // remove item from set rather than
                                  // create a whole new set
        }
    })
    return result;
}

比较

所以现在让我们比较两者,如果你感觉很幸运,试着猜测性能差异,这是衡量你对Javascript执行的理解的好方法。

// Create a set of numbers from 0 and < count
// With a odds for any number occurring to be odds
// return as a new set;
function createLargeSet(count,odds){
    var numbers = new Set();
    while(count-- > 0){
        if(Math.random() < odds){
            numbers.add(count);
        }
    }
    return numbers;
}
// create a array of large sets
function bigArrayOfSets(setCount,setMaxSize,odds){
    var bigSets = [];
    for(var i = 0; i < setCount; i ++){
        bigSets.push(createLargeSet(setMaxSize,odds));
    }
    return bigSets;
}
function intersectA(firstSet,...sets) {
    var count = sets.length;
    var result = new Set(firstSet); // Only create one copy of the set
    firstSet.forEach(item => {
        var i = count;
        var allHave = true;
        while(i--){
            allHave = sets[i].has(item)
            if(!allHave) { break }  // loop only until item fails test
        }
        if(!allHave){
            result.delete(item);  // remove item from set rather than
                                  // create a whole new set
        }
    })
    return result;
}

function intersectB(firstSet, ...sets) {
    var intersect = (a,b) => {
        return new Set([...a].filter(item => b.has(item)))
    };
    sets.forEach(sItem => firstSet = intersect(firstSet, sItem));
    return firstSet;
}
var testSets = [];
for(var i = 0.1; i <= 1; i += 0.1){
    testSets.push(bigArrayOfSets(100,10000,i));
}

var now = performance.now();
testSets.forEach(testDat => intersectB(...testDat));
var time = performance.now() - now;
console.log("Execution time 'intersectB' : " + time);

var now = performance.now();
testSets.forEach(testDat => intersectA(...testDat));
var time = performance.now() - now;
console.log("Execution time 'intersectA' : " + time);

正如您所看到的,使用简单的while循环可能不像使用过滤器那样酷,但性能优势很大,下次您编写完美的3行ES6阵列操作函数时要记住这一点。不要忘记forwhile

答案 3 :(得分:1)

好吧我猜测执行数组交集的最有效方法是使用Map或Hash对象。在这里,我测试了1000个数组,每个数组在1..175之间有〜1000个随机整数项,用于交集。结果在不到100毫秒的时间内获得。

&#13;
&#13;
function setIntersection(a){
  var m = new Map(),
      r = new Set(),
      l = a.length;
  a.forEach(sa => new Set(sa).forEach(n => m.has(n) ? m.set(n,m.get(n)+1)
                                                    : m.set(n,1)));
  m.forEach((v,k) => v === l && r.add(k));
  return r;
}

var testSets = Array(1000).fill().map(_ => Array(1000).fill().map(_ => ~~(Math.random()*175+1)));
console.time("int");
result = setIntersection(testSets);
console.timeEnd("int");
console.log(JSON.stringify([...result]));
&#13;
&#13;
&#13;

答案 4 :(得分:1)

相交n个数组的最有效算法是在fast_array_intersect中实现的算法。它在O(n)中运行,其中n是所有数组中元素的总数。

基本原理很简单:遍历所有数组,存储您在地图中看到每个元素的次数。然后过滤最小的数组,以仅返回在所有数组中可见的元素。 (source code)

您可以使用一个简单的库:

import intersect from 'fast_array_intersect'

intersect([[1,2,3], [1,2,6]]) // --> [1,2]