在多个JavaScript数组之间查找匹配项

76

我有多个包含字符串值的数组,希望比较它们并仅保留在所有数组中都相同的匹配结果。

给定以下示例代码:

var arr1 = ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'];
var arr2 = ['taco', 'fish', 'apple', 'pizza'];
var arr3 = ['banana', 'pizza', 'fish', 'apple'];

我希望生成一个包含所有给定数组中匹配项的以下数组:

['apple', 'fish', 'pizza']
我知道可以用var newArr = arr1.concat(arr2, arr3);组合所有的数组,但这只会给我一个包含所有元素(包括重复元素)的数组。有没有简单的方法可以做到这一点,而不需要使用像underscore.js这样的库?
(太好了,现在我又饿了!) 编辑:我想我应该提到可能会有未知数量的数组,我只是举了三个例子。

看一下这个链接:https://dev59.com/kXI-5IYBdhLWcg3wYnOQ - nbrooks
13个回答

103
var result = arrays.shift().filter(function(v) {
    return arrays.every(function(a) {
        return a.indexOf(v) !== -1;
    });
});

演示:http://jsfiddle.net/nWjcp/2/

您可以首先对外部数组进行排序,以便在开头获取最短的数组...

arrays.sort(function(a, b) {
    return a.length - b.length;
});

为了完整起见,这里提供一个处理数组中重复项的解决方案。它使用.reduce()而不是.filter()...
var result = arrays.shift().reduce(function(res, v) {
    if (res.indexOf(v) === -1 && arrays.every(function(a) {
        return a.indexOf(v) !== -1;
    })) res.push(v);
    return res;
}, []);

演示:http://jsfiddle.net/nWjcp/4/


1
@Derek:这对我来说也是最近才发现的。看看MDN文档吧,第二个参数真的很棒。而且,你不需要传递一个数字,它可以是一个字符串,用作缩进字符。 - user1106925
1
@amnotiam 是的,它们嵌套在另一个数组中。我想我需要更好地提供示例代码...哈! - FiniteLooper
1
@ChrisBarr:为了覆盖所有情况,我添加了一个解决方案,以相同的方式处理重复项。它在底部。 - user1106925
1
@amnotiam 非常感谢,这太棒了!我真的需要学习更多关于这些内置方法的知识,它们非常强大。 - FiniteLooper
1
@TomB.:基本上是删除所有出现在所有列表中的项目。这是一种方法:http://jsfiddle.net/nWjcp/296,虽然不是最有效的方法,因为它可能将每个数组的所有项与其他所有数组进行比较,但它可以完成任务。 - user1106925
显示剩余10条评论

33

假设有一个由数组组成的数组,我们希望找到它们的交集,最简单的一行代码是:

var arr = [[0,1,2,3,4,5,6,7,8,9],[0,2,4,6,8],[4,5,6,7]],
    int = arr.reduce((p,c) => p.filter(e => c.includes(e)));

document.write("<pre>" + JSON.stringify(int) + "</pre>");


4
我不知道为什么这不是正确答案 - 我知道;它是在4年后给出的... 但它应该是正确的!谢谢@Redu! - Pedro Ferreira
1
@Edmund Reed 谢谢... p 代表前一个值,c 代表当前值,在没有初始值的 reduce 操作中是常用的变量。e 代表元素,是所有数组方法回调函数中非常常见的变量名。 - Redu
有没有办法修复当 arr = [] 时发生的异常,或者必须事先检查? - Otto Abnormalverbraucher
@Redu 不行。当你尝试在空数组上执行.reduce(...)时,会出现异常情况。你描述了arr=[[]]的情况,在这种情况下,你的数组中有一个空条目,但如果根本没有条目,操作就会失败。 - Otto Abnormalverbraucher
1
@Otto Abnormalverbraucher,正如我在回答中提到的,我们假设有一个数组的数组,其基本情况为 [[]]。然而...这是一个很好的评论。当传入一个空数组时,异常信息显示,这里的 .reduce() 没有使用初始值来开始。因此,不用再多说,也许像 arr.length ? arr.reduce((p,c) => p.filter(e => c.includes(e))) : []; 这样做就足够了。 - Redu
显示剩余3条评论

16

现在,您已经添加了不确定数量的数组到问题中,这里有另一种方法,它将每个项目的计数收集到一个对象中,然后汇总具有最大计数的项目。

这种方法的优点:

  1. 如果数组较大,则比其他答案使用的暴力搜索选项快约15倍
  2. 不需要ES5或ES5 shim(适用于所有浏览器)
  3. 完全无破坏性(完全不修改源数据)
  4. 处理源数组中的重复项
  5. 处理任意数量的输入数组

以下是代码:

function containsAll(/* pass all arrays here */) {
    var output = [];
    var cntObj = {};
    var array, item, cnt;
    // for each array passed as an argument to the function
    for (var i = 0; i < arguments.length; i++) {
        array = arguments[i];
        // for each element in the array
        for (var j = 0; j < array.length; j++) {
            item = "-" + array[j];
            cnt = cntObj[item] || 0;
            // if cnt is exactly the number of previous arrays, 
            // then increment by one so we count only one per array
            if (cnt == i) {
                cntObj[item] = cnt + 1;
            }
        }
    }
    // now collect all results that are in all arrays
    for (item in cntObj) {
        if (cntObj.hasOwnProperty(item) && cntObj[item] === arguments.length) {
            output.push(item.substring(1));
        }
    }
    return(output);
}    

工作演示: http://jsfiddle.net/jfriend00/52mAP/

顺便说一句,这不需要 ES5,因此可以在所有浏览器中正常工作,无需 shim。

在对 15 个每个数组包含 1000 个元素进行性能测试时,它比 am not i am 在这个 jsperf 中使用的搜索方法快了 10 倍以上: http://jsperf.com/in-all-arrays


这是一个使用 ES6 的 MapSet 来去重和跟踪计数的版本。 它的优点是数据类型得到保留并且可以使任何类型的数据(它甚至不必具有自然字符串转换,数据甚至可以是对象,虽然对象用于比较是否为完全相同的对象,而不是具有相同的属性/值)。

var arrays = [
    ['valueOf', 'toString','apple', 'orange', 'banana', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza', 1, 2, 999, 888],
    ['valueOf', 'toString','taco', 'fish', 'fish', 'apple', 'pizza', 1, 999, 777, 999, 1],
    ['valueOf', 'toString','banana', 'pizza', 'fish', 'apple', 'apple', 1, 2, 999, 666, 555]
    ];
    
// subclass for updating cnts    
class MapCnt extends Map {
    constructor(iterable) {
        super(iterable);
    }
    
    cnt(iterable) {
        // make sure items from the array are unique
        let set = new Set(iterable);
        // now update the cnt for each item in the set
        for (let item of set) {
            let cnt = this.get(item) || 0;
            ++cnt;
            this.set(item, cnt);
        }
    }
}


function containsAll(...allArrays) {
    let cntObj = new MapCnt();
    for (array of allArrays) {
        cntObj.cnt(array);
    }
    // now see how many items have the full cnt
    let output = [];
    for (var [item, cnt] of cntObj.entries()) {
        if (cnt === allArrays.length) {
            output.push(item);
        }
    }
    return(output);
}    

var result = containsAll.apply(this, arrays);

document.body.innerHTML = "<pre>[<br>    " + result.join(',<br>    ') + "<br>]</pre>";


调整了算法以处理重复项,并添加了性能测试,以展示它比其他一些方法快多少(快14倍)。 - jfriend00
@RobG - 我修改了代码,使其能够在IE8或任何其他内置方法中使用“toString”、“valueOf”。为此,我向每个键添加了前缀,以将其与任何内置方法区分开来。 - jfriend00
另一种方法是在普通对象上添加Object.prototype属性的测试,以查看哪些属性从未枚举,然后在for..in结束后对它们进行测试。 - RobG
我在我的答案中添加了一个获取列表的函数。当IE 8及以下版本不再使用时,这将变得无关紧要(假设在IE 9+和sycophants中已经修复)。 - RobG
我最终用C++写了它,但不管怎样,很高兴看到ES6终于得到了一些理智的数据结构:) 这只花了大约20年的时间。太遗憾了,它可能不会很快被我正在使用的框架所支持:https://bugreports.qt.io/browse/QTBUG-47735 - dtech
显示剩余6条评论

3

有几点建议:你可以仅比较最短数组中的项,并防止返回数组中的重复项。

function arraysInCommon(arrays){
    var i, common,
    L= arrays.length, min= Infinity;
    while(L){
        if(arrays[--L].length<min){
            min= arrays[L].length;
            i= L;
        }
    }
    common= arrays.splice(i, 1)[0];
    return common.filter(function(itm, indx){
        if(common.indexOf(itm)== indx){
            return arrays.every(function(arr){
                return arr.indexOf(itm)!= -1;
            });
        }
    });
}

var arr1= ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'];
var arr2= ['taco', 'fish', 'apple', 'pizza', 'apple','apple'];
var arr3= ['banana', 'pizza', 'fish', 'apple','fish'];

var allArrays = [arr1,arr2,arr3];

arraysInCommon(allArrays).sort();

返回值:苹果、鱼、披萨

演示 - http://jsfiddle.net/kMcud/


3
    // The easiest way!! 
    
    var arr1 = ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'];
    var arr2 = ['taco', 'fish', 'apple', 'pizza'];
    var arr3 = ['banana', 'pizza', 'fish', 'apple'];
    var arr4 = [];


    for(let i of arr1){
      if(arr2.includes(i) && arr3.includes(i)){
        arr4.push(i)
      }
    }

    console.log(arr4)


------------- OR -----------------


arr4 = arr1.filter(value => arr2.includes(value) && arr3.includes(value))

这个对我来说最有意义。谢谢! - Jimmy De Los Angeles
@JimmyDeLosAngeles 你也可以查看更新后的版本!! - md_salm

2

假设有一个数组的数组,并检查所有数组:

演示:http://jsfiddle.net/qUQHW/

var tmp = {};
for (i = 0; i < data.length; i++) {
    for (j = 0; j < data[i].length; j++) {
        if (!tmp[data[i][j]]) {
            tmp[data[i][j]] = 0;
        }
        tmp[data[i][j]]++;
    }
}

var results = $.map(tmp, function(val,key) {
    return val == data.length ? key :null;
})

2

这里有一个单行解决方案。您可以将其分为两个思考步骤:

  1. 计算两个数组之间的交集/并集

var arrA = [1,2,3,4,5];
var arrB = [4,5,10];
var innerJoin = arrA.filter(el=>arrB.includes(el));
console.log(`Intersection is: ${innerJoin}`);

  1. 减少内容:计算累积交集和下一个数组之间的交集。

var arrays = [
 ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'],
 ['taco', 'fish', 'apple', 'pizza'],
 ['banana', 'pizza', 'fish', 'apple']
];
var join = arrays.reduce((join, current) => join.filter(el => current.includes(el)));
console.log(`Intersection is: ${join}`);


当使用这两个数组作为测试用例时,这段代码无法正常工作。[1,2,2,1],[2]。它应该返回[2],但实际上返回了[2, 2]。 - iPzard

1

这应该适用于任何数量的数组:

function intersection(arr1, arr2) {
  var temp = [];

  for (var i in arr1) {
    var element = arr1[i];

    if (arr2.indexOf(element) > -1) {
      temp.push(element);
    }
  }

  return temp;
}

function multi_intersect() {
  var arrays = Array.prototype.slice.apply(arguments).slice(1);
  var temp = arguments[0];

  for (var i in arrays) {
    temp = intersection(arrays[i], temp);

    if (temp == []) {
      break;
    }
  }

  return temp;
}

var arr1 = ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'];
var arr2 = ['taco', 'fish', 'apple', 'pizza'];
var arr3 = ['banana', 'pizza', 'fish', 'apple'];

multi_intersect(arr1, arr2, arr3);

0

只是为了好玩,另一种冗长的方法:

function getCommon(a) {

  // default result is copy of first array
  var result = a[0].slice();
  var mem, arr, found = false;

  // For each member of result, see if it's in all other arrays
  // Go backwards so can splice missing entries
  var i = result.length;

  while (i--) {
    mem = result[i];

    // Check in each array
    for (var j=1, jLen=a.length; j<jLen; j++) {
      arr = a[j];
      found = false;

      // For each member of arr and until found
      var k = arr.length;
      while (k-- && !found) {

        // If found in this array, set found to true
        if (mem == arr[k]) {
          found = true;
        }
      }
      // if word wasn't found in this array, remove it from result and 
      // start on next member of result, skip remaining arrays.
      if (!found) {
        result.splice(i,1);
        break;
      }
    }
  }
  return result;
}

var data = [
  ['taco', 'fish', 'apple', 'pizza', 'mango', 'pear'],
  ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'],
  ['banana', 'pizza', 'fish', 'apple'],
  ['banana', 'pizza', 'fish', 'apple', 'mango', 'pear']
];

编辑

查找基于Object.prototype上的不可枚举属性的函数:

// Return an array of Object.prototype property names that are not enumerable
// even when added directly to an object.
// Can be helpful with IE as properties like toString are not enumerable even
// when added to an object.
function getNeverEnumerables() {

    // List of Object.prototype property names plus a random name for testing
    var spNames = 'constructor toString toLocaleString valueOf ' +
                  'hasOwnProperty isPrototypeOf propertyIsEnumerable foo';

    var spObj = {foo:'', 'constructor':'', 'toString':'', 'toLocaleString':'', 'valueOf':'',
                 'hasOwnProperty':'', 'isPrototypeOf':'', 'propertyIsEnumerable':''};

    var re = [];

    // BUild list of enumerable names in spObj
    for (var p in spObj) {
      re.push(p); 
    }

    // Remove enumerable names from spNames and turn into an array
    re = new RegExp('(^|\\s)' + re.join('|') + '(\\s|$)','g');
    return spNames.replace(re, ' ').replace(/(^\s+)|\s\s+|(\s+$)/g,'').split(' ');
}

document.write(getNeverEnumerables().join('<br>'));

0

这本质上是所有答案的汇编:

  // Intersect any number of arrays:

 function intersect() {

   // - Arguments -> traditional array,
   // - First item ( arrays[0] ) = shortest to reduce iterations
   var arrays = Array.prototype.slice.call(arguments).sort(function(a, b) {
     return a.length - b.length;
   });

   // Use first array[0] as the base.
   var a = arrays.shift();

   var result = [];
   for (var i = a.length; i--;) {

     var val = a[i];

     // Prevent duplicates
     if (result.indexOf(val) < 0) {

       // Seek
       var found = true;
       for (var ii = arrays.length; ii--;) {
         if (arrays[ii].indexOf(val) < 0) {
           found = false;
           break;
         }
       }

       if (found) {
         result.push(val);
       }

     }

   }

   return result;

 }

 /*
 // Slower, but smaller code-base:
 function intersect (){
  
  // - Arguments -> traditional array,
  // - First item ( arrays[0] ) = shortest to reduce iterations
  var arrays = Array.prototype.slice.call(arguments).sort(function(a, b) {
         return a.length - b.length;
     });
  
  // Use first array[0] as the base.
  var a = arrays.shift();

  return a.filter(function (val, idx, aa) {
   
      // Seek
                  for(var i=arrays.length; i--;){
                      if (arrays[i].indexOf(val) < 0) {
           return false;
          }
                  }
      
      // Prevent duplicates
                  return aa.indexOf(val) === idx;
  
     });

 }
 */

 var arr1 = ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'];
 var arr2 = ['taco', 'fish', 'apple', 'pizza', 'apple', 'apple'];
 var arr3 = ['banana', 'pizza', 'fish', 'apple', 'fish'];

 var arr1 = ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'];
 var arr2 = ['taco', 'fish', 'apple', 'pizza', 'apple', 'apple'];
 var arr3 = ['banana', 'pizza', 'fish', 'apple', 'fish'];


 var result = intersect(arr1, arr2, arr3);

  // For fiddle output:
 var elem = document.getElementById("result");
 elem.innerHTML = JSON.stringify(result);
 console.log(result);
<div id="result">Results</div>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接