题 如何在JavaScript中合并两个数组并重复删除项目


我有两个JavaScript数组:

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

我希望输出为:

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

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

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


978
2017-10-18 08:34


起源




答案:


仅合并数组(不删除重复项)

ES5版本使用 Array.concat

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

var array3 = array1.concat(array2); // Merges both arrays
// [ 'Vijendra', 'Singh', 'Singh', 'Shakya' ]

ES6版本使用 解构

const array1 = ["Vijendra","Singh"];
const array2 = ["Singh", "Shakya"];
const array3 = [...array1, ...array2];

由于没有'内置'方法来删除重复项(ECMA-262 实际上有 Array.forEach 这对我来说很棒),我们必须手动完成:

Array.prototype.unique = function() {
    var a = this.concat();
    for(var i=0; i<a.length; ++i) {
        for(var j=i+1; j<a.length; ++j) {
            if(a[i] === a[j])
                a.splice(j--, 1);
        }
    }

    return a;
};

然后,使用它:

var array1 = ["Vijendra","Singh"];
var array2 = ["Singh", "Shakya"];
// Merges both arrays and gets unique items
var array3 = array1.concat(array2).unique(); 

这也将保留数组的顺序(即,不需要排序)。

由于许多人对原型增强感到恼火 Array.prototype 和 for in 循环,这是一种使用它的侵入性较小的方法:

function arrayUnique(array) {
    var a = array.concat();
    for(var i=0; i<a.length; ++i) {
        for(var j=i+1; j<a.length; ++j) {
            if(a[i] === a[j])
                a.splice(j--, 1);
        }
    }

    return a;
}

var array1 = ["Vijendra","Singh"];
var array2 = ["Singh", "Shakya"];
    // Merges both arrays and gets unique items
var array3 = arrayUnique(array1.concat(array2));

对于那些有幸使用ES5可用的浏览器的人,可以使用 Object.defineProperty 喜欢这个:

Object.defineProperty(Array.prototype, 'unique', {
    enumerable: false,
    configurable: false,
    writable: false,
    value: function() {
        var a = this.concat();
        for(var i=0; i<a.length; ++i) {
            for(var j=i+1; j<a.length; ++j) {
                if(a[i] === a[j])
                    a.splice(j--, 1);
            }
        }

        return a;
    }
});

1237
2017-10-18 08:42



注意,该算法是O(n ^ 2)。 - Gumbo
让 [a, b, c] 和 [x, b, d] 是数组(假设引号)。 concat给出了 [a, b, c, x, b, d]。独特()的输出不会 [a, c, x, b, d]。这不保留我认为的顺序 - 我相信OP想要的 [a, b, c, x, d] - Amarghosh
OP接受了让他工作的第一个答案,并且似乎签了字。我们仍在比较彼此的解决方案,发现修复错误,提高性能,确保其兼容性等等...... stackoverflow的美丽:-) - Amarghosh
我最初投了票,但改变了主意。将原型分配给Array.prototype具有打破“for ... in”语句的后果。因此,最好的解决方案可能是使用这样的函数,但不要将其指定为原型。有些人可能会争辩说“for ... in”语句不应该用于迭代数组元素,但是人们经常以这种方式使用它们,所以至少这个解决方案应该谨慎使用。 - Code Commander
你应该经常使用 for ... in 同 hasOwnProperty 在这种情况下,原型方法很好 - mulllhausen


使用Underscore.js或Lo-Dash,您可以:

_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2, 3, 101, 10]

http://underscorejs.org/#union

http://lodash.com/docs#union


510
2018-05-08 13:24



或者,甚至可能比下划线更好,API兼容 lodash。 - Brian M. Hunt
@Ygg来自lodash docs。 “返回一个新的唯一值数组, 为了,存在于一个或多个阵列中。“ - Richard Ayotte
我更喜欢underscore.js。我最终使用的是 underscore.flatten(),它比union更好,因为它需要一个数组数组。 - weaver
@weaver _.flatten合并,但不会“删除重复”。 - GijsjanB
快速演绎lodash与最佳答案: jsperf.com/merge-two-arrays-keeping-only-unique-values - slickplaid


首先连接两个数组,然后只筛选出唯一的项。

var a = [1, 2, 3], b = [101, 2, 1, 10];
var c = a.concat(b);
var d = c.filter(function (item, pos) {return c.indexOf(item) == pos});

// d is [1,2,3,101,10]

http://jsfiddle.net/simo/98622/

编辑

正如@Dmitry所建议的那样(参见下面的第二条评论),更明智的解决方案是过滤掉中的唯一项目 b 在连接之前 a

var a = [1, 2, 3], b = [101, 2, 1, 10];
var c = a.concat(b.filter(function (item) {
    return a.indexOf(item) < 0;
}));

// d is [1,2,3,101,10]

194
2018-04-15 10:08



这里的原始解决方案具有删除每个源阵列中的欺骗的好处。我想这取决于您将使用的上下文。 - theGecko
您可以为IE6支持合并不同的内容:c = Array.from(new Set(c)); - Tobi G.
如果我想真正改变 a 加上 b那么循环并使用推送会更好吗? a.forEach(function(item){ if(a.indexOf(item)<0) a.push(item); }); - awe
只是提醒当前的浏览器使用情况 caniuse.com/usage-table 对于那些对IE6感到焦虑的人。 - pmrotule
@Andrew:更好: 1。  var c = [...a, ...b.filter(o => !~a.indexOf(o))];  2。  var c = [...new Set([...a, ...b])];  - 7vujy0f0hy


这是一个使用ECMAScript 6的解决方案 传播运营商 和数组泛型。

目前它只适用于Firefox,可能还适用于Internet Explorer Technical Preview。

但如果你使用 巴别塔,你现在可以拥有它。

// Input: [ [1, 2, 3], [101, 2, 1, 10], [2, 1] ]
// Output: [1, 2, 3, 101, 10]
function mergeDedupe(arr)
{
  return [...new Set([].concat(...arr))];
}

119
2017-12-27 06:28



这应该添加到接受的答案中。这个解决方案比现在的解决方案更有效,更优雅,但这是我们不可避免的能够做到的(并且应该做到这一点)。 - EmmaGamma
这与OP的问题不一样(这似乎更像是一个平面图而不是任何东西),但是因为它很棒而赞成。 - jedd.ahyoung
@ jedd.ahyoung:做吧 mergeDedupe(...arr) 你可以称之为 mergeDedupe(array1, array2) 这正是OP想要的。 - Bergi
很难说这应该是接受的答案,因为问题是从2009年开始。但是,是的,这不仅更具“高性能”而且“优雅” - Cezar Augusto
Array.from 可以用来代替传播运算符: Array.from(new Set([].concat(...arr))) - Henry Blyth


ES6

array1.push(...array2) // => don't remove duplication 

要么

[...array1,...array2] //   =>  don't remove duplication 

要么

[...new Set([...array1 ,...array2])]; //   => remove duplication

86
2017-08-14 08:08



第1 /第2例是否定的 union 在所有+第一个例子中炸大堆 Arrays +第3个例子非常慢,消耗大量内存,因为两个中间 Arrays必须构建+第3个例子只能用于 union 已知数量的 Array在编译时。 - ftor
那你怎么做呢? - David Noreña
Set 是去这里的方式 - philk
请注意,对于set,不能对具有相同键值对的两个对象进行重复数据删除,除非它们是相同的对象引用。 - Jun
应该接受现代的Javascript答案。 - Nguyen Thanh


这是一个稍微不同的循环。通过最新版Chrome中的一些优化,它是解决两个阵列联合的最快方法(Chrome 38.0.2111)。

http://jsperf.com/merge-two-arrays-keeping-only-unique-values

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

var arr = array1.concat(array2),
  len = arr.length;

while (len--) {
  var itm = arr[len];
  if (array3.indexOf(itm) === -1) {
    array3.unshift(itm);
  }
}

while循环:~589k ops / s
过滤器:~445k ops / s
lodash:308k ops / s
for循环:225k ops / s

注释指出我的一个设置变量导致我的循环领先于其余变量,因为它不必初始化要写入的空数组。我同意这一点,所以我已经将测试改写为公平竞争,并且包括更快的选项。

http://jsperf.com/merge-two-arrays-keeping-only-unique-values/21

var whileLoopAlt = function(array1, array2) {
    var array3 = [];
    var arr = array1.concat(array2);
    var len = arr.length;
    var assoc = {};

    while(len--) {
        var itm = arr[len];

        if(!assoc[itm]) { // Eliminate the indexOf call
            array3.unshift(itm);
            assoc[itm] = true;
        }
    }

    return array3;
};

在这个替代解决方案中,我结合了一个答案的关联数组解决方案来消除 .indexOf() 在循环中调用,通过第二个循环减慢了很多事情,并包括其他用户在他们的答案中建议的一些其他优化。

这里的最佳答案是每个值(i-1)上的双循环仍然明显变慢。 lodash仍然很强大,我还是会推荐给那些不介意在他们的项目中添加库的人。对于那些不想要的人,我的while循环仍然是一个很好的答案,过滤器的答案在这里有很强的表现,在撰写本文时,最新的Canary Chrome(44.0.2360)的测试结果都是如此。

查看 迈克的回答 和 丹斯托克的答案 如果你想提高速度的一个档次。在经历了几乎所有可行的答案之后,这些是迄今为止所有结果中最快的。


30
2017-08-04 14:14



您的方法存在一个缺陷:您将array3的创建置于设置阶段,而该成本应该只是基于时间的解决方案得分的一部分。 随着这一行移动,你的解决方案降低到基于for循环的速度。我知道数组可以重用,但也许其他算法也可以从不必声明和初始化每个必要的构建块中受益。 - doldt
很好,@ doldt。 - user633183
我同意你的前提@doldt,但不同意你的结果。基于循环的条目删除存在一个基本的设计缺陷,因为您必须在删除项目后重新检查数组的长度,从而导致执行时间变慢。向后工作的while循环没有这些效果。下面是一个示例,尽可能多地删除设置变量而不会过多地更改其原始答案: jsperf.com/merge-two-arrays-keeping-only-unique-values/19 - slickplaid
@slickplaid链接的测试是空的,jsperf的下一个修订版在while循环中挂起。 - doldt
@slickplaid感谢您设置扩展的perf页面。除非我遗漏了什么,“whileLoopAlt2”功能不起作用?它创建一个包含第一个数组的新数组,以及第二个数组(反向顺序)。为了避免混淆,我做了另一个修订,删除了破坏的功能。我还添加了一个例子: jsperf.com/merge-two-arrays-keeping-only-unique-values/22 - Stephen S


用一个  (ECMAScript 2015),它将如此简单:

const array1 = ["Vijendra", "Singh"];
const array2 = ["Singh", "Shakya"];
const array3 = Array.from(new Set(array1.concat(array2)));

27
2018-01-06 19:05



我认为这是使用ES6的“已接受的答案”。 - mwieczorek
@mwieczorek怎么样: const array3 = [...new Set(array1.concat(array2))] - Robby Cornelissen
更好...... - mwieczorek


你可以用ECMAScript 6简单地完成它,

var array1 = ["Vijendra", "Singh"];
var array2 = ["Singh", "Shakya"];
var array3 = [...new Set([...array1 ,...array2])];
console.log(array3); // ["Vijendra", "Singh", "Shakya"];
  • 使用 传播运营商 用于连接数组。
  • 使用  用于创建一组独特的元素。
  • 再次使用spread运算符将Set转换为数组。

26
2018-04-07 07:26



我收到错误:类型'Set <string>'不是数组类型。 - gattsbr
如果您出于某种原因不想使用扩展运算符,那么还有: Array.from(new Set(array1.concat(array2)))。 - kba
完善!谢谢。 - GollyJer


Array.prototype.merge = function(/* variable number of arrays */){
    for(var i = 0; i < arguments.length; i++){
        var array = arguments[i];
        for(var j = 0; j < array.length; j++){
            if(this.indexOf(array[j]) === -1) {
                this.push(array[j]);
            }
        }
    }
    return this;
};

一个更好的阵列合并功能。


14
2017-08-19 13:34



var test = ['a', 'b', 'c']; console.log(test);  将打印 ["a", "b", "c", merge: function] - Doubidou
优秀的解决方我已经更新了@slickplaid上面发布的jsperf测试(jsperf.com/merge-two-arrays-keeping-only-unique-values/3)看起来这是其中最快的一个。 - Cobra
@Cobra冒着听起来很小的风险,在Chrome 40.0.2214上运行(2015年2月18日最新),这个答案比我的慢了53%。 OTOH IE11似乎没有针对我的答案进行优化。但是,Chrome移动设备仍在摇摆不定。老实说,如果你使用我们大多数人应该使用的lodash / _,真正的答案已经在这个列表上相当高了。 :) - slickplaid
@slickplaid是的,即使与lodash / _一个相比,它也要快得多。我可能最终会在某个时刻将我的实现切换到类似于你的实现。 :d - Cobra


刚扔掉我的两分钱。

function mergeStringArrays(a, b){
    var hash = {};
    var ret = [];

    for(var i=0; i < a.length; i++){
        var e = a[i];
        if (!hash[e]){
            hash[e] = true;
            ret.push(e);
        }
    }

    for(var i=0; i < b.length; i++){
        var e = b[i];
        if (!hash[e]){
            hash[e] = true;
            ret.push(e);
        }
    }

    return ret;
}

这是我经常使用的一种方法,它使用一个对象作为hashlookup表来进行重复检查。假设散列是O(1),那么这在O(n)中运行,其中n是a.length + b.length。老实说,我不知道浏览器如何处理哈希,但它在数千个数据点上表现良好。


13
2017-12-12 19:50



做得很好。通过利用关联数组并阻止indexOf和其他操作的循环,在此页面上击败其他结果(如果不是全部)。 jsperf.com/merge-two-arrays-keeping-only-unique-values/21 - slickplaid
你的“哈希”是 String() 在javascript中的功能。这可能适用于原始值(尽管类型之间存在冲突),但它不适合对象数组。 - Bergi
我使用类似的解决方案,我允许传递hashCode函数或传递字符串来标识对象中的属性以用作散列键。 - Robert Baker