查找多个 JavaScript 数组之间的匹配

我有多个具有字符串值的数组,我想对它们进行比较,并且只保留它们的 全部之间相同的匹配结果。

给定这个示例代码:

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 这样的库的开销?

(太好了,我也饿了!)

EDIT 我想我应该提到可能存在未知数量的数组,我只是用3作为一个例子。

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

演示: 《 http://jsfiddle.net/nwjcp/2/》(http://jsfiddle.net/nWjcp/2/)

您可以首先对外部 Array 进行排序,以便在开始时获得最短的 Array..。

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/》(http://jsfiddle.net/nWjcp/4/”)

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

这种方法的优点:

  1. 如果数组更大的话,比蛮力搜索选项(其他答案使用)快15倍
  2. 不需要 ES5或 ES5垫片(适用于所有浏览器)
  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,所以将在所有的浏览器没有垫片工作。

在对每1000个长度的15个数组进行的性能测试中,这比在 am not i am 中使用的搜索方法快10倍以上。


下面是一个使用 ES6MapSet来分解和跟踪计数的版本。这样做的好处是数据的类型可以被保留,可以是任何类型(它甚至不需要自然的字符串转换,数据甚至可以是对象,尽管对象被比较为完全相同的对象,没有相同的属性/值)。

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

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

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

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

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

只是为了好玩,另一个长手的方法:

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']
];

剪辑

函数查找永远不可枚举的属性:

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

一些想法——你可以比较最短数组中的项目, 并防止在返回的数组中出现重复。

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/

假设有一个数组数组,我们希望找到它们的交集,一个最简单的单线性方法可以是

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

这基本上是所有答案的汇编,归纳如下:

	 // 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>

这里有一个单行的解决方案,你可以把它分成两个思考步骤:

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

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

你可以使用 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));

另一个解决办法:

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

这种方法计算每个项的数量。迭代所有数组后,将选择等于数组数的计数。

为了通用起见,该函数接受一个数组数组和比较函数,以便在使用对象而不是原始值的情况下生成映射键。

它使用第一个数组中的实例作为引用,返回一个公共值数组。

function commonValues<T>(arrays: T[][], keyFn: (item: T) => string): T[] {
const counts: Record<any, { count: number, item: T }> = {}
for (const array of arrays) {
for (const item of array) {
const key = keyFn(item)
let entry = counts[key]
if (!entry) {
entry = {count: 0, item}
counts[key] = entry
}
entry.count++
}
}
return Object.values(counts)
.filter(it => it.count === arrays.length)
.map(it => it.item)
}

//对于这个基本示例,key 函数只返回字符串。

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


const common = commonItems([arr1,arr2,arr3], it=>it)

function commonValues(arrays, keyFn) {
const counts = {}
for (const array of arrays) {
for (const item of array) {
const key = keyFn(item)
let entry = counts[key]
if (!entry) {
entry = {count: 0, item}
counts[key] = entry
}
entry.count++
}
}
return Object.values(counts)
.filter(it => it.count === arrays.length)
.map(it => it.item)
}


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


const found = commonValues([arr1,arr2,arr3], it=>it)


console.log('common', found)