在JavaScript中,实现数组交集的最简单、无需使用库的代码是什么?我想编写
intersection([1,2,3], [2,3,4,5])
并获取
[2, 3]
在JavaScript中,实现数组交集的最简单、无需使用库的代码是什么?我想编写
intersection([1,2,3], [2,3,4,5])
并获取
[2, 3]
使用 Array.prototype.filter
和 Array.prototype.includes
的组合:
const filteredArray = array1.filter(value => array2.includes(value));
对于较旧的浏览器,使用 Array.prototype.indexOf
并且没有箭头函数:
var filteredArray = array1.filter(function(n) {
return array2.indexOf(n) !== -1;
});
注意!.includes
和.indexOf
内部使用===
来比较数组中的元素,因此如果数组包含对象,则只会比较对象引用(而不是它们的内容)。如果要指定自己的比较逻辑,请改用Array.prototype.some
。
intersection([1,2,1,1,3], [1])
返回 [1, 1, 1]
。它难道不应该只返回 [1]
吗? - dev如果我们可以假设输入已经排序,那么"destructive"似乎是最简单的方法:
/* 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;
}
.shift
需要移动数组中的每个元素(本身是O(n)),因此第一个版本复杂度的评论是错误的。 - Esailija如果您的环境支持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) {
var setB = new Set(b);
return [...new Set(a)].filter(x => setB.has(x));
}
请注意,使用集合时,您将仅获得不同的值,因此 new Set([1, 2, 3, 3]).size
将计算为 3
。
[...setA]
语法是什么?是一种特殊的JavaScript操作吗? - jxramosSet
的优势是什么?为什么不能只用标准数组来完成它? - adelriosantiagoa
和intersection
都创建一个集合。只需要其中一个即可。 - Ry-使用 Underscore.js 或 lodash.js
_.intersection( [0,345,324] , [1,0,324] ) // gives [0,324]
arrA.filter(e=>e.includes(arrB))
或者_.intersection(arrA, arrB)
? - Ivancho[ [ { tokenId: 2 } ], [ { tokenId: 2 }, { tokenId: 3 } ] ]
并期望它返回{ tokenId: 2 }
? - ansh sachdeva
var objects = [ [ { tokenId: 2 } ] ]
var others = [ [
{ tokenId: 2 },
{ tokenId: 3 }
] ]
_.intersectionWith(objects, others, (a, b) => _.intersectionWith(a, b));
- Harshil Modi// 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进行性能比较。
function intersect_for(a, b) {
const result = [];
const alen = a.length;
const blen = b.length;
for (let i = 0; i < alen; ++i) {
const ai = a[i];
for (let j = 0; j < blen; ++j) {
if (ai === b[j]) {
result.push(ai);
break;
}
}
}
return result;
}
function intersect_filter_indexOf(a, b) {
return a.filter(el => b.indexOf(el) !== -1);
}
function intersect_filter_in(a, b) {
const map = b.reduce((map, el) => {map[el] = true; return map}, {});
return a.filter(el => el in map);
}
function intersect_for_in(a, b) {
const result = [];
const map = {};
for (let i = 0, length = b.length; i < length; ++i) {
map[b[i]] = true;
}
for (let i = 0, length = a.length; i < length; ++i) {
if (a[i] in map) result.push(a[i]);
}
return result;
}
function intersect_filter_includes(a, b) {
return a.filter(el => b.includes(el));
}
function intersect_filter_has_this(a, b) {
return a.filter(Set.prototype.has, new Set(b));
}
function intersect_filter_has_arrow(a, b) {
const set = new Set(b);
return a.filter(el => set.has(el));
}
function intersect_for_has(a, b) {
const result = [];
const set = new Set(b);
for (let i = 0, length = a.length; i < length; ++i) {
if (set.has(a[i])) result.push(a[i]);
}
return result;
}
在 Firefox 53 中的结果:
Ops/sec on large arrays (10,000 elements):
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
Ops/sec on small arrays (100 elements):
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]
。 - SeregPie用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
。 - Reduprototype
。 - freganteArray.prototype
,那么请正确地进行操作。 - Bergi最简单、最快的O(n)和最短的方法:
function intersection (a, b) {
const setA = new Set(a);
return b.filter(value => setA.has(value));
}
console.log(intersection([1,2,3], [2,3,4,5]))
@nbarbosa几乎给出了相同的答案,但他将两个数组都转换为Set
,然后再转回array
。不同之处在于,他的代码只会返回唯一的记录,而此代码的结果还将包含来自数组b
的重复条目(假设两个数组没有填入唯一值)。
所以使用符合您要求的任何代码即可。
使用关联数组如何?
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
时,这才有可能成功。 - Tim Downd[b[i]] = true;
而不是 d[b[j]] = true;
(使用 i
而不是 j
)。但修改需要 6 个字符。 - Izhaki如果您需要处理相交的多个数组:
const intersect = (a1, a2, ...rest) => {
const a12 = a1.filter(value => a2.includes(value))
if (rest.length === 0) { return a12; }
return intersect(a12, ...rest);
};
console.log(intersect([1,2,3,4,5], [1,2], [1, 2, 3,4,5], [2, 10, 1]))
b.includes(x)
比使用new Set(b).has(x)
甚至更慢。 - Ry-使用 .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创建了一个基准测试。使用.pop方法可以使速度快三倍。
a[aLast] > b[bLast]
替换为 a[aLast].localeCompare(b[bLast]) > 0
(并且下面的 else if
同样如此),那么它将适用于字符串。 - andrew.pop
的时间复杂度是 O(1),而 .shift()
的时间复杂度是 O(n)。 - Esailija
break
添加到Simple js loops
中将会使 ops/sec 增加到约10M。 - Richard