【问题标题】:Finding matches between multiple JavaScript Arrays查找多个 JavaScript 数组之间的匹配项
【发布时间】:2012-06-20 00:32:30
【问题描述】:

我有多个带有字符串值的数组,我想比较它们,只保留 ALL 之间相同的匹配结果。

鉴于此示例代码:

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 等库的开销吗?

(太好了,现在我也饿了!)

编辑我想我应该提到可能存在未知数量的数组,我只是以 3 为例。

【问题讨论】:

标签: javascript jquery arrays


【解决方案1】:
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/

【讨论】:

  • @Derek:: 这对我来说也是一个相对较新的发现。查看 MDN 文档。第二个论点真的很巧妙。此外,您不需要传递数字。它可以是一个将用作缩进字符的字符串。
  • @amnotiam 是的,它们嵌套在另一个数组中。我想我需要变得更好并提供示例代码......哈!
  • @ChrisBarr:为了涵盖所有基础,我添加了一个相同风格的解决方案来处理重复项。它在底部。
  • @amnotiam 规则,非常感谢!我真的需要更多地了解这些内置方法,它们很强大。
  • @TomB.:您的意思是 1) 保留未出现在 any 其他数组中的数组中的项目,还是2) 保留未出现在至少一个 其他数组中的项目?因此,如果“pizza”在第一个和第二个数组中,但不在第三个数组中,则根据解释 1 包含但将根据 2.
【解决方案2】:

假设有一个数组数组,我们想要找到它们的交集,最简单的单行方法可能是

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年后给的……但它应该!发送@Redu!
  • 完美——这样的事情总是有一个很棒的 1 班轮。我唯一的建议是给参数起更有意义的名字,因为我不知道pce 代表什么。
  • @Edmund Reed 谢谢... 变量p 用于先前的变量和c 用于当前的变量是reduce 的常规操作,没有初始值。 e for element 是所有数组方法回调中非常常见的变量名。
  • 有没有办法修复arr = []时出现的异常或者必须事先检查?
  • @Otto Abnormalverbraucher 正如我在回答中提到的,我们假设有一个数组数组,其基本情况为[[]]。然而......足够公平的评论。当输入一个空数组时,异常提到,.reduce() 这里不使用初始值开始。所以事不宜迟,也许像arr.length ? arr.reduce((p,c) =&gt; p.filter(e =&gt; c.includes(e))) : []; 这样就足够了。
【解决方案3】:

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

这种方法的优点:

  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 的所有浏览器中使用。

在对每 1000 个长度的 15 个数组进行性能测试时,这比我在此 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 倍)。
  • +1 我喜欢containsAll 方法,我在考虑基于对象的方法,但没有使用计数器。很好地处理 dups 而不将它们从原始数组中删除。我认为很多速度来自于避免使用 splice 和 slice 之类的数组方法,对象属性查找可能是高度优化的,因为它对于任何重要的脚本都是如此基础。
  • 哦,这种方法的一个问题是,至少在 IE 8 及更低版本中,属性“toString”和“valueOf”总是不可枚举的,所以如果有问题的数组将这些名称作为成员值,上面永远不会把它们放在结果数组中。一种解决方案是显式测试item 上的这些值。
  • @RobG - 我修改了代码以在 IE8 或任何其他内置方法中使用 "toString""valueOf"。为此,我为每个键添加了一个前缀,以将其与任何内置方法区分开来。
  • ——另一种方法是在一个普通对象上添加一个 Object.prototype 属性的测试,看看哪些是永远不可枚举的,然后在最后的 for..in 之后测试它们。
【解决方案4】:

几个想法 - 你可以只比较最短数组中的项目, 并防止返回的数组出现重复。

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

返回值:apple,fish,pizza

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

【讨论】:

    【解决方案5】:

    假设数组数组并检查所有数组:

    演示: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;
    })
    

    【讨论】:

      【解决方案6】:

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

      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]。
      【解决方案7】:
          // 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))
      

      【讨论】:

      • 这个对我来说最有意义。谢谢!
      • @JimmyDeLosAngeles 你也可以查看更新的!!
      【解决方案8】:

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

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

      【讨论】:

        【解决方案9】:

        顺便说一句,另一种长手方法:

        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 上基于 thise 查找永远无法枚举的属性的功能:

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

        【讨论】:

          【解决方案10】:

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

          	 // 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);
          &lt;div id="result"&gt;Results&lt;/div&gt;

          【讨论】:

            【解决方案11】:

            您可以使用array#reducearray#filter。对于每个数组,获取所有唯一值并在 Map 查找中并保持计数。完成后,array#filter 根据数组长度进行此查找。

            const commonElements = (...arr) => {
              const lookup = arr.reduce((map, a) => {
                const unique = [...new Set(a)];
                unique.forEach(v => {
                  map.set(v, (map.get(v) || 0) + 1)
                });
                return map;
              },new Map());
              return [...lookup.keys()].filter(k => lookup.get(k) === arr.length);
            }
            
            const arr1 = ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'],
                  arr2 = ['taco', 'fish', 'apple', 'pizza'],
                  arr3 = ['banana', 'pizza', 'fish', 'apple'];
            console.log(commonElements(arr1,arr2,arr3));

            【讨论】:

              【解决方案12】:

              另一种解决方案:

              const arr1 = ['apple', 'orange', 'banana', 'pear', 'fish', 'pancake', 'taco', 'pizza'];
              const arr2 = ['taco', 'fish', 'apple', 'pizza'];
              const arr3 = ['banana', 'pizza', 'fish', 'apple'];
              const combinedArr = [arr1, arr2, arr3];
              
              const result  = combinedArr
                  .flatMap(([...values]) => values)
                  .filter((value, index, coll) => (coll.indexOf(value) === index) && combinedArr.every(
                      (values) => values.includes(value)
                  ));
                  
              console.log(result);
              .as-console-wrapper { max-height: 100% !important; top: 0; }

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2022-09-23
                • 2017-10-11
                • 2017-08-06
                • 2020-07-14
                • 2014-04-09
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多