在javascript中实现数组交集的最简单,无库代码是什么?我想写
intersection([1,2,3], [2,3,4,5])
并得到
[2, 3]
break
到Simple js loops
增加操作/秒至约10M
在javascript中实现数组交集的最简单,无库代码是什么?我想写
intersection([1,2,3], [2,3,4,5])
并得到
[2, 3]
break
到Simple js loops
增加操作/秒至约10M
Answers:
使用的组合Array.prototype.filter
和Array.prototype.indexOf
:
array1.filter(value => -1 !== array2.indexOf(value))
或者,如vrugtehagel在注释中建议的那样,您可以使用更新Array.prototype.includes
的代码甚至更简单的代码:
array1.filter(value => array2.includes(value))
对于较旧的浏览器:
array1.filter(function(n) {
return array2.indexOf(n) !== -1;
});
intersection([1,2,1,1,3], [1])
返回[1, 1, 1]
。它不应该回来[1]
吗?
array2.indexOf(n) != -1
编写一个array2.includes(n)
更简单的代码外,还可以编写一个。
破坏性似乎最简单,特别是如果我们可以假设输入已排序:
/* destructively finds the intersection of
* two arrays in a simple fashion.
*
* PARAMS
* a - first array, must already be sorted
* b - second array, must already be sorted
*
* NOTES
* State of input arrays is undefined when
* the function returns. They should be
* (prolly) be dumped.
*
* Should have O(n) operations, where n is
* n = MIN(a.length, b.length)
*/
function intersection_destructive(a, b)
{
var result = [];
while( a.length > 0 && b.length > 0 )
{
if (a[0] < b[0] ){ a.shift(); }
else if (a[0] > b[0] ){ b.shift(); }
else /* they're equal */
{
result.push(a.shift());
b.shift();
}
}
return result;
}
由于我们必须跟踪索引,因此非破坏性的处理必须更加复杂。
/* finds the intersection of
* two arrays in a simple fashion.
*
* PARAMS
* a - first array, must already be sorted
* b - second array, must already be sorted
*
* NOTES
*
* Should have O(n) operations, where n is
* n = MIN(a.length(), b.length())
*/
function intersect_safe(a, b)
{
var ai=0, bi=0;
var result = [];
while( ai < a.length && bi < b.length )
{
if (a[ai] < b[bi] ){ ai++; }
else if (a[ai] > b[bi] ){ bi++; }
else /* they're equal */
{
result.push(a[ai]);
ai++;
bi++;
}
}
return result;
}
intersect_safe
:length
是数组中的属性,而不是方法。有一个不受欢迎的变量i
在result.push(a[i]);
。最后,这在一般情况下根本行不通:根据>
运算符,两个对象都不大于另一个的对象不一定相等。intersect_safe( [ {} ], [ {} ] )
例如,将给(一旦修复了前面提到的错误)具有一个元素的数组,这显然是错误的。
.slice(0)
在中创建数组的克隆intersect_safe
,而不是跟踪索引。
如果您的环境支持ECMAScript 6 Set,那么这是一种简单有效的方法(请参阅规范链接):
function intersect(a, b) {
var setA = new Set(a);
var setB = new Set(b);
var intersection = new Set([...setA].filter(x => setB.has(x)));
return Array.from(intersection);
}
较短,但可读性较差(也没有创建其他交集Set
):
function intersect(a, b) {
return [...new Set(a)].filter(x => new Set(b).has(x));
}
每次都避免新Set
来的东西b
:
function intersect(a, b) {
var setB = new Set(b);
return [...new Set(a)].filter(x => setB.has(x));
}
请注意,使用集时,您只会获得不同的值,因此new Set[1,2,3,3].size
计算结果为3
。
[...setA]
语法?一些特殊的javascript操作?
x => new Set(b).has(x)
箭头功能不是每次执行都b
变成一个集合吗?您可能应该将该集合保存在变量中。
使用 Underscore.js或lodash.js
_.intersection( [0,345,324] , [1,0,324] ) // gives [0,324]
我对ES6的贡献。通常,它会找到一个数组与作为参数提供的不确定数量的数组的交集。
Array.prototype.intersect = function(...a) {
return [this,...a].reduce((p,c) => p.filter(e => c.includes(e)));
}
var arrs = [[0,2,4,6,8],[4,5,6,7],[4,6]],
arr = [0,1,2,3,4,5,6,7,8,9];
document.write("<pre>" + JSON.stringify(arr.intersect(...arrs)) + "</pre>");
[[0,1,2,3,4,5,6,7,8,9],[0,2,4,6,8],[4,5,6,7],[4,6]]
,然后应用.reduce()
。[0,1,2,3,4,5,6,7,8,9].filter( e => [0,2,4,6,8].includes(e)
执行第一个操作后,结果将变为新结果,p
并在下一轮中c
变为结果[4,5,6,7]
,并依此类推,直到不再有c
剩余为止。
prototype
不必要地修改。
// Return elements of array a that are also in b in linear time:
function intersect(a, b) {
return a.filter(Set.prototype.has, new Set(b));
}
// Example:
console.log(intersect([1,2,3], [2,3,4,5]));
我推荐以上简洁的解决方案,该解决方案在大输入量方面胜过其他实现。如果小投入的性能很重要,请检查以下替代方案。
替代方案和性能比较:
请参阅以下代码段以了解替代实现,并检查https://jsperf.com/array-intersection-comparison以进行性能比较。
Firefox 53中的结果:
大型阵列(10,000个元素)上的运算/秒:
filter + has (this) 523 (this answer)
for + has 482
for-loop + in 279
filter + in 242
for-loops 24
filter + includes 14
filter + indexOf 10
小型阵列(100个元素)上的运算/秒:
for-loop + in 384,426
filter + in 192,066
for-loops 159,137
filter + includes 104,068
filter + indexOf 71,598
filter + has (this) 43,531 (this answer)
filter + has (arrow function) 35,588
intersect([1,2,2,3], [2,3,4,5])
返回[2, 2, 3]
。
a.filter(b.includes)
。它应该运行得更快(与您的功能升级相同)。
仅使用关联数组怎么样?
function intersect(a, b) {
var d1 = {};
var d2 = {};
var results = [];
for (var i = 0; i < a.length; i++) {
d1[a[i]] = true;
}
for (var j = 0; j < b.length; j++) {
d2[b[j]] = true;
}
for (var k in d1) {
if (d2[k])
results.push(k);
}
return results;
}
编辑:
// new version
function intersect(a, b) {
var d = {};
var results = [];
for (var i = 0; i < b.length; i++) {
d[b[i]] = true;
}
for (var j = 0; j < a.length; j++) {
if (d[a[j]])
results.push(a[j]);
}
return results;
}
Object.prototype
。
d[b[i]] = true;
代替d[b[j]] = true;
(i
not j
)。但是编辑需要6个字符。
这样的东西,虽然没有很好的测试。
function intersection(x,y){
x.sort();y.sort();
var i=j=0;ret=[];
while(i<x.length && j<y.length){
if(x[i]<y[j])i++;
else if(y[j]<x[i])j++;
else {
ret.push(x[i]);
i++,j++;
}
}
return ret;
}
alert(intersection([1,2,3], [2,3,4,5]));
PS:仅适用于数字和普通字符串的算法,任意对象数组的交集可能不起作用。
通过使用.pop而不是.shift可以改善@atk对基元排序数组的实现的性能。
function intersect(array1, array2) {
var result = [];
// Don't destroy the original arrays
var a = array1.slice(0);
var b = array2.slice(0);
var aLast = a.length - 1;
var bLast = b.length - 1;
while (aLast >= 0 && bLast >= 0) {
if (a[aLast] > b[bLast] ) {
a.pop();
aLast--;
} else if (a[aLast] < b[bLast] ){
b.pop();
bLast--;
} else /* they're equal */ {
result.push(a.pop());
b.pop();
aLast--;
bLast--;
}
}
return result;
}
我使用jsPerf创建了一个基准测试:http ://bit.ly/P9FrZK 。使用.pop的速度大约快三倍。
a[aLast] > b[bLast]
与a[aLast].localeCompare(b[bLast]) > 0
(和相同的else if
下方),那么这将在字符串的工作。
.pop
是O(1)和.shift()
O(n)
对于仅包含字符串或数字的数组,您可以按照其他一些答案进行排序。对于任意对象数组的一般情况,我认为您不能避免这样做。下面将为您提供作为参数提供的任意数量的数组的交集arrayIntersection
:
var arrayContains = Array.prototype.indexOf ?
function(arr, val) {
return arr.indexOf(val) > -1;
} :
function(arr, val) {
var i = arr.length;
while (i--) {
if (arr[i] === val) {
return true;
}
}
return false;
};
function arrayIntersection() {
var val, arrayCount, firstArray, i, j, intersection = [], missing;
var arrays = Array.prototype.slice.call(arguments); // Convert arguments into a real array
// Search for common values
firstArray = arrays.pop();
if (firstArray) {
j = firstArray.length;
arrayCount = arrays.length;
while (j--) {
val = firstArray[j];
missing = false;
// Check val is present in each remaining array
i = arrayCount;
while (!missing && i--) {
if ( !arrayContains(arrays[i], val) ) {
missing = true;
}
}
if (!missing) {
intersection.push(val);
}
}
}
return intersection;
}
arrayIntersection( [1, 2, 3, "a"], [1, "a", 2], ["a", 1] ); // Gives [1, "a"];
firstArr
或firstArray
不更新所有引用的想法。固定。
使用ES2015和Sets很短。接受类似String的类似数组的值,并删除重复项。
let intersection = function(a, b) {
a = new Set(a), b = new Set(b);
return [...a].filter(v => b.has(v));
};
console.log(intersection([1,2,1,2,3], [2,3,5,4,5,3]));
console.log(intersection('ccaabbab', 'addb').join(''));
你可以使用一个Set
作为thisArg
的Array#filter
,并采取Set#has
回调。
function intersection(a, b) {
return a.filter(Set.prototype.has, new Set(b));
}
console.log(intersection([1, 2, 3], [2, 3, 4, 5]));
对此处的最小调整进行细微调整(filter / indexOf解决方案),即使用JavaScript对象在其中一个数组中创建值的索引,会将其从O(N * M)减少为“大概”线性时间。源1 源2
function intersect(a, b) {
var aa = {};
a.forEach(function(v) { aa[v]=1; });
return b.filter(function(v) { return v in aa; });
}
这不是最简单的解决方案(它比filter + indexOf的代码更多),也不是最快的(可能比intersect_safe()慢一个常数),但似乎是一个很好的平衡。它具有非常简单的一面,同时提供了良好的性能,并且不需要预先排序的输入。
另一种索引方法能够一次处理任意数量的数组:
// Calculate intersection of multiple array or object values.
function intersect (arrList) {
var arrLength = Object.keys(arrList).length;
// (Also accepts regular objects as input)
var index = {};
for (var i in arrList) {
for (var j in arrList[i]) {
var v = arrList[i][j];
if (index[v] === undefined) index[v] = 0;
index[v]++;
};
};
var retv = [];
for (var i in index) {
if (index[i] == arrLength) retv.push(i);
};
return retv;
};
它仅适用于可以评估为字符串的值,您应该将它们作为数组传递,例如:
intersect ([arr1, arr2, arr3...]);
...但是它透明地接受对象作为参数或要相交的任何元素(总是返回公共值数组)。例子:
intersect ({foo: [1, 2, 3, 4], bar: {a: 2, j:4}}); // [2, 4]
intersect ([{x: "hello", y: "world"}, ["hello", "user"]]); // ["hello"]
编辑:我只是注意到,从某种意义上来说,这是有问题的。
那就是:我对它进行了编码,以为输入数组本身不能包含重复(如提供的示例所没有)。
但是,如果输入数组碰巧包含重复,那将产生错误的结果。示例(使用以下实现):
intersect ([[1, 3, 4, 6, 3], [1, 8, 99]]);
// Expected: [ '1' ]
// Actual: [ '1', '3' ]
幸运的是,只需添加二级索引即可轻松解决此问题。那是:
更改:
if (index[v] === undefined) index[v] = 0;
index[v]++;
通过:
if (index[v] === undefined) index[v] = {};
index[v][i] = true; // Mark as present in i input.
...和:
if (index[i] == arrLength) retv.push(i);
通过:
if (Object.keys(index[i]).length == arrLength) retv.push(i);
完整的例子:
// Calculate intersection of multiple array or object values.
function intersect (arrList) {
var arrLength = Object.keys(arrList).length;
// (Also accepts regular objects as input)
var index = {};
for (var i in arrList) {
for (var j in arrList[i]) {
var v = arrList[i][j];
if (index[v] === undefined) index[v] = {};
index[v][i] = true; // Mark as present in i input.
};
};
var retv = [];
for (var i in index) {
if (Object.keys(index[i]).length == arrLength) retv.push(i);
};
return retv;
};
intersect ([[1, 3, 4, 6, 3], [1, 8, 99]]); // [ '1' ]
var v =
行添加之后if (typeof v == 'function') continue;
,它将跳过向结果中添加函数的操作。谢谢!
if (typeof v == 'function')
,则可以将它们的字符串化(v.toString()
)作为索引的键来检测它们。但是,我们需要做一些事情以使其完整无损。这样做的最简单方法就是分配原来的功能价值,而不是一个简单的布尔值true但是,在这种情况下,最新的deindexaton也应改变,以检测这种情况并恢复正确的价值(功能)。
您可以使用(适用于IE以外的所有浏览器):
const intersection = array1.filter(element => array2.includes(element));
或IE:
const intersection = array1.filter(element => array2.indexOf(element) !== -1);
在数据上有一些限制,您可以在线性时间内完成!
对于正整数:使用将值映射到“可见/不可见”布尔值的数组。
function intersectIntegers(array1,array2) {
var seen=[],
result=[];
for (var i = 0; i < array1.length; i++) {
seen[array1[i]] = true;
}
for (var i = 0; i < array2.length; i++) {
if ( seen[array2[i]])
result.push(array2[i]);
}
return result;
}
对于对象,有一种类似的技术:取一个虚拟密钥,将其设置为array1中每个元素的“ true”,然后在array2的元素中查找该密钥。完成后清理。
function intersectObjects(array1,array2) {
var result=[];
var key="tmpKey_intersect"
for (var i = 0; i < array1.length; i++) {
array1[i][key] = true;
}
for (var i = 0; i < array2.length; i++) {
if (array2[i][key])
result.push(array2[i]);
}
for (var i = 0; i < array1.length; i++) {
delete array1[i][key];
}
return result;
}
当然,您需要确保密钥之前没有出现,否则您将销毁数据...
IE 9.0,Chrome,Firefox,Opera的“ indexOf”
function intersection(a,b){
var rs = [], x = a.length;
while (x--) b.indexOf(a[x])!=-1 && rs.push(a[x]);
return rs.sort();
}
intersection([1,2,3], [2,3,4,5]);
//Result: [2,3]
'use strict'
// Example 1
function intersection(a1, a2) {
return a1.filter(x => a2.indexOf(x) > -1)
}
// Example 2 (prototype function)
Array.prototype.intersection = function(arr) {
return this.filter(x => arr.indexOf(x) > -1)
}
const a1 = [1, 2, 3]
const a2 = [2, 3, 4, 5]
console.log(intersection(a1, a2))
console.log(a1.intersection(a2))
功能方法必须考虑仅使用无副作用的纯函数,而每个副作用仅与一项工作有关。
这些限制增强了所涉及功能的可组合性和可重用性。
// small, reusable auxiliary functions
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));
const apply = f => x => f(x);
// intersection
const intersect = xs => ys => {
const zs = createSet(ys);
return filter(x => zs.has(x)
? true
: false
) (xs);
};
// mock data
const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,3,6,7,8,9];
// run it
console.log( intersect(xs) (ys) );
请注意,使用了本机Set
类型,它具有优越的查找性能。
显然,从第一个重复出现的项Array
被保留,而第二Array
个重复项被去重复。这可能是或可能不是所需的行为。如果您需要唯一的结果,只需将其应用于dedupe
第一个参数:
// auxiliary functions
const apply = f => x => f(x);
const comp = f => g => x => f(g(x));
const afrom = apply(Array.from);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));
// intersection
const intersect = xs => ys => {
const zs = createSet(ys);
return filter(x => zs.has(x)
? true
: false
) (xs);
};
// de-duplication
const dedupe = comp(afrom) (createSet);
// mock data
const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,3,6,7,8,9];
// unique result
console.log( intersect(dedupe(xs)) (ys) );
Array
s 的交集如果你要计算的任意数目的交集Array
的只有我撰写intersect
有foldl
。这是一个便捷功能:
// auxiliary functions
const apply = f => x => f(x);
const uncurry = f => (x, y) => f(x) (y);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));
const foldl = f => acc => xs => xs.reduce(uncurry(f), acc);
// intersection
const intersect = xs => ys => {
const zs = createSet(ys);
return filter(x => zs.has(x)
? true
: false
) (xs);
};
// intersection of an arbitrarily number of Arrays
const intersectn = (head, ...tail) => foldl(intersect) (head) (tail);
// mock data
const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,3,6,7,8,9];
const zs = [0,1,2,3,4,5,6];
// run
console.log( intersectn(xs, ys, zs) );
(expr ? true : false)
是多余的。仅expr
在不需要实际布尔值的情况下使用,只在真实/虚假的情况下使用。
为简单起见:
// Usage
const intersection = allLists
.reduce(intersect, allValues)
.reduce(removeDuplicates, []);
// Implementation
const intersect = (intersection, list) =>
intersection.filter(item =>
list.some(x => x === item));
const removeDuplicates = (uniques, item) =>
uniques.includes(item) ? uniques : uniques.concat(item);
// Example Data
const somePeople = [bob, doug, jill];
const otherPeople = [sarah, bob, jill];
const morePeople = [jack, jill];
const allPeople = [...somePeople, ...otherPeople, ...morePeople];
const allGroups = [somePeople, otherPeople, morePeople];
// Example Usage
const intersection = allGroups
.reduce(intersect, allPeople)
.reduce(removeDuplicates, []);
intersection; // [jill]
优点:
缺点:
您不想将其用于3D引擎或内核工作,但是如果您在使它在基于事件的应用程序中运行时遇到问题,则设计会遇到更大的问题。
除了list1.filter(n => list2.includes(n)),这可能是最简单的一个
var list1 = ['bread', 'ice cream', 'cereals', 'strawberry', 'chocolate']
var list2 = ['bread', 'cherry', 'ice cream', 'oats']
function check_common(list1, list2){
list3 = []
for (let i=0; i<list1.length; i++){
for (let j=0; j<list2.length; j++){
if (list1[i] === list2[j]){
list3.push(list1[i]);
}
}
}
return list3
}
check_common(list1, list2) // ["bread", "ice cream"]
如果需要让它处理相交的多个数组:
const intersect = (a, b, ...rest) => {
if (rest.length === 0) return [...new Set(a)].filter(x => new Set(b).has(x));
return intersect(a, intersect(b, ...rest));
};
console.log(intersect([1,2,3,4,5], [1,2], [1, 2, 3,4,5], [2, 10, 1])) // [1,2]
ES6风格简单的方法。
const intersection = (a, b) => {
const s = new Set(b);
return a.filter(x => s.has(x));
};
例:
intersection([1, 2, 3], [4, 3, 2]); // [2, 3]
我编写了一个积分函数,该函数甚至可以根据那些对象的特定属性来检测对象数组的交集。
例如,
if arr1 = [{id: 10}, {id: 20}]
and arr2 = [{id: 20}, {id: 25}]
并且我们希望基于该id
属性的交集,则输出应为:
[{id: 20}]
因此,相同的功能(请注意:ES6代码)为:
const intersect = (arr1, arr2, accessors = [v => v, v => v]) => {
const [fn1, fn2] = accessors;
const set = new Set(arr2.map(v => fn2(v)));
return arr1.filter(value => set.has(fn1(value)));
};
您可以将函数调用为:
intersect(arr1, arr2, [elem => elem.id, elem => elem.id])
另请注意:此函数在考虑第一个数组是主数组的情况下找到交集,因此交集结果将是主数组的交集结果。
这是underscore.js的实现:
_.intersection = function(array) {
if (array == null) return [];
var result = [];
var argsLength = arguments.length;
for (var i = 0, length = array.length; i < length; i++) {
var item = array[i];
if (_.contains(result, item)) continue;
for (var j = 1; j < argsLength; j++) {
if (!_.contains(arguments[j], item)) break;
}
if (j === argsLength) result.push(item);
}
return result;
};
资料来源:http : //underscorejs.org/docs/underscore.html#section-62