如何在JavaScript中合并两个数组并去重

1962

我有两个 JavaScript 数组:

var array1 = ["Vijendra","Singh"];
var array2 = ["Singh", "Shakya"];

我希望输出结果是:

var array3 = ["Vijendra","Singh","Shakya"];

输出数组应该删除重复的单词。

我如何在JavaScript中合并两个数组,以便获取每个数组中唯一的项,并以它们插入原始数组的相同顺序返回?


32
在发布新答案之前,请考虑此问题已经有75个以上的答案。请确保您的答案提供的信息不在现有答案中。 - janniks
5
结果为 [1, 2, 3, 4],这行代码使用了 ES6 的 Set 数据结构和展开运算符将两个数组去重合并。 - Denis Giffeler
如果您想要一个更通用的解决方案,也包括深度合并,请查看这个问题。一些答案也涵盖了数组。 - Martin Braun
简而言之 - 合并数组 (ba) : a=a.concat(b); 从数组 a 中删除重复项 (就地操作) : a=a.filter((i,p)=>a.indexOf(i)===p); - ashleedawg
如果你不想再有更多答案,可以关闭问题。 - Janos Vinceller
问题仍然开放,等待不同、创新、前沿的答案。这也是免责声明的原因。 - Rodrigo Rodrigues
92个回答

4

使用ES2015的函数式方法

遵循函数式方法,将两个Array合并成一个union只需使用concatfilter的组合。为了提供最佳性能,我们使用本地的Set数据类型,该类型经过优化以进行属性查找。

无论如何,与union函数相关的关键问题是如何处理重复项。以下排列方式是可能的:

Array A      + Array B

[unique]     + [unique]
[duplicated] + [unique]
[unique]     + [duplicated]
[duplicated] + [duplicated]

前两个排列很容易用单个函数处理。但是,后两个排列更加复杂,因为如果您依赖于Set查找,就无法处理它们。由于转换为普通的Object属性查找会带来严重的性能损失,因此以下实现仅忽略第三个和第四个排列。您需要构建一个单独的版本来支持它们。


// small, reusable auxiliary functions

const comp = f => g => x => f(g(x));
const apply = f => a => f(a);
const flip = f => b => a => f(a) (b);
const concat = xs => y => xs.concat(y);
const afrom = apply(Array.from);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));


// de-duplication

const dedupe = comp(afrom) (createSet);


// the actual union function

const union = xs => ys => {
  const zs = createSet(xs);  
  return concat(xs) (
    filter(x => zs.has(x)
     ? false
     : zs.add(x)
  ) (ys));
}


// mock data

const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,4,5,6,6];


// here we go

console.log( "unique/unique", union(dedupe(xs)) (ys) );
console.log( "duplicated/unique", union(xs) (ys) );

从这里开始实现unionn函数就变得很简单了,它接受任意数量的数组(受到naomik评论的启发):

// small, reusable auxiliary functions

const uncurry = f => (a, b) => f(a) (b);
const foldl = f => acc => xs => xs.reduce(uncurry(f), acc);

const apply = f => a => f(a);
const flip = f => b => a => f(a) (b);
const concat = xs => y => xs.concat(y);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));


// union and unionn

const union = xs => ys => {
  const zs = createSet(xs);  
  return concat(xs) (
    filter(x => zs.has(x)
     ? false
     : zs.add(x)
  ) (ys));
}

const unionn = (head, ...tail) => foldl(union) (head) (tail);


// mock data

const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,4,5,6,6];
const zs = [0,1,2,3,4,5,6,7,8,9];


// here we go

console.log( unionn(xs, ys, zs) );

事实证明,unionn只是foldl(又名Array.prototype.reduce),它以union作为其reducer。注意:由于实现不使用额外的累加器,因此在没有参数的情况下应用它会抛出错误。


1
一些反馈:我注意到 flipnotf 没有被使用。此外,unionBy 的谓词泄漏了实现细节(需要隐式知道 Set 类型)。如果你能像这样做就好了:union = unionBy (apply)unionci = unionBy (p => x => p(x.toLowerCase()))。这样用户只需将分组值发送到 p 中 - 只是一个想法 ^_^ - Mulan
zs变量声明也缺少var/let关键字。 - Mulan
1
这里有一段代码片段以澄清:[gist: unionBy.js] - Mulan
@naomik 经过一段时间的重新思考,我不再确定传递谓词的方式是否正确。你所获得的只是第二个数组中每个元素的转换。我想知道这种方法是否解决了更多的问题,而不仅仅是玩具问题。 - user6445533
在这种情况下,函数式方法有哪些好处? - Kamil Kiełczewski

4

这里是针对具有对象数组的选项:

const a = [{param1: "1", param2: 1},{param1: "2", param2: 2},{param1: "4", param2: 4}]
const b = [{param1: "1", param2: 1},{param1: "4", param2: 5}]


var result = a.concat(b.filter(item =>
         !JSON.stringify(a).includes(JSON.stringify(item))
    ));

console.log(result);
//Result [{param1: "1", param2: 1},{param1: "2", param2: 2},{param1: "4", param2: 4},{param1: "4", param2: 5}]

4

var array1 = ["one","two"];
var array2 = ["two", "three"];
var collectionOfTwoArrays = [...array1, ...array2];    
var uniqueList = array => [...new Set(array)];
console.log('Collection :');
console.log(collectionOfTwoArrays);    
console.log('Collection without duplicates :');
console.log(uniqueList(collectionOfTwoArrays));


3

仅仅是为了好玩,这里提供一个单行解决方案:

const x = [...new Set([['C', 'B'],['B', 'A']].reduce( (a, e) => a.concat(e), []))].sort()
// ['A', 'B', 'C']

虽然不太容易理解,但可以帮助某些人:

  1. 使用初始累加器值设置为空数组的reduce函数。
  2. reduce函数使用concat将每个子数组附加到累加器数组上。
  3. 这个结果作为构造函数参数传递给创建一个新的Set
  4. 使用展开操作符将Set转换为数组。
  5. 对新数组应用sort()函数。

2
另外,你可以使用 Array.from(set) 代替 reduce() - Eran Goldin

3
var arr1 = [1, 3, 5, 6];
var arr2 = [3, 6, 10, 11, 12];
arr1.concat(arr2.filter(ele => !arr1.includes(ele)));
console.log(arr1);

output :- [1, 3, 5, 6, 10, 11, 12]

3

如果您不想出现特定属性(例如ID)的重复

let noDuplicate = array1.filter ( i => array2.findIndex(a => i.id==a.id)==-1 );
let result = [...noDuplicate, ...array2];

3
如果您想合并对象数组,请考虑使用lodash的UnionBy函数,它允许您设置自定义谓词来比较对象。
import { unionBy } from 'lodash';

const a = [{a: 1, b: 2}];
const b = [{a: 1, b: 3}];
const c = [{a: 2, b: 4}];

const result = UnionBy(a,b,c, x => x.a);

结果为:[{ a: 1; b: 2 }, { a: 2; b: 4 }]

结果使用来自数组的第一个匹配项


谢谢。我相信这是最好的解决方案。 - Sandokan

3

模块化、通用

这可以通过组合两个基本功能来实现。

const getUniqueMerge = (...arrs) => getUniqueArr(mergeArrs(...arrs))
const getUniqueArr = (array) => Array.from(new Set(array))  
const mergeArrs = (...arrs) => [].concat(...arrs)

可以处理无限的数组或值。
console.log(getUniqueMerge(["Vijendra","Singh"],["Singh", "Shakya"])
// ["Vijendra", "Singh", "Shakya"]

console.log(getUniqueMerge(["Sheldon", "Cooper"], ["and", "Cooper", "Amy", "and"], "Farrah", "Amy", "Fowler"))
// ["Sheldon", "Cooper", "and", "Amy", "Farrah", "Fowler"]

3
你可以尝试这个:

const union = (a, b) => Array.from(new Set([...a, ...b]));

console.log(union(["neymar","messi"], ["ronaldo","neymar"]));


3
  1. 使用array.concat()array.filter()方法
  2. 使用新的Set对象展开运算符
  3. 使用array.concat()方法和新的Set对象

注意:本文中的代码示例涉及到了HTML标签,请勿删除。

let array1 = [1, 2, 3, 4, 5]
let array2 = [1, 4, 6, 9]

// Using array.concat and array.filter
const array3 = array1.concat(array2.filter((item)=> array1.indexOf(item) == -1 ))
console.log('array3 : ', array3);

// Using new Set and Spread Operator
const array4 = [...new Set([...array1 ,...array2])];
console.log('array4 : ', array4);

// Using array.concat and new Set
const array5 = [...new Set(array1.concat(array2))];
console.log('array5 : ', array5);


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