如何在JavaScript中获取两个数组之间的差异?

1333

有没有一种方法可以在 JavaScript 中返回两个数组的差异?

例如:

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];

// need ["c", "d"]

16
对称还是非对称? - Lightness Races in Orbit
2
使用新的ES6函数,这可以作为一个简单的一行代码完成(在所有主要浏览器中使用它需要很长时间)。无论如何,请查看我的答案 - Salvador Dali
2
解决方案的一个重要方面是性能。这种类型操作的渐进时间复杂度 - 在其他语言中 - 是 O(a1.length x log(a2.length)) - 这种性能在JavaScript中是否可能? - Raul
请查看我的库,它可以帮助您解决这个问题,@netilon/differify 是用于对象/数组比较的最快速的差异库之一: https://www.npmjs.com/package/@netilon/differify - Fabian Orue
1
  1. 将a1转换为集合。o(a1)。
  2. 迭代e2以查看它具有而e1没有的内容。o(e2)。
  3. 将差异推入另一个数组,然后在完成步骤2后返回它。
- powerup7
您可以使用筛选器,检查我的答案https://dev59.com/aXM_5IYBdhLWcg3w3nbz#74317431 - Manu Rastogi
84个回答

2884

有一种更好的方法,使用ES7:


交集

 let intersection = arr1.filter(x => arr2.includes(x));

交集差异维恩图

对于[1,2,3] [2,3],它将产生[2,3]。另一方面,对于[1,2,3] [2,3,5]将返回相同的结果。


差异

let difference = arr1.filter(x => !arr2.includes(x));

Right difference Venn Diagram

对于 [1,2,3] [2,3],它将返回[1]。另一方面,对于[1,2,3] [2,3,5]会返回相同的结果。


对于对称差,可以这样做:

let difference = arr1
                 .filter(x => !arr2.includes(x))
                 .concat(arr2.filter(x => !arr1.includes(x)));

对称差分维恩图

这样,你将得到一个包含 arr1 中所有不在 arr2 中的元素以及反之的数组。

正如 @Joshaven Potter 在他的答案中指出的那样,你可以将这个方法添加到 Array.prototype 中,这样就可以像这样使用:

Array.prototype.diff = function(arr2) { return this.filter(x => !arr2.includes(x)); }
[1, 2, 3].diff([2, 3])

11
计算“数组差异”是一种所谓的“集合操作”,因为属性查找是Set的独特职责,而SetindexOf/includes快得多。简单来说,你的解决方案非常低效,而且相对较慢。 - user6445533
2
@ftor 但是使用 Set,值必须是唯一的,对吗? - CervEd
3
我理解如果数字是唯一的,那么对于[1,2,3] [2,3,5]这组数据会奏效,但如果你有[1,1,2,3] [1,2,3,5]并期望得到[1],你不能使用Set。虽然你的解决方案也行不通 :-/ 我最终创建了这个函数,因为我找不到更简洁的方法来完成它。如果你有任何想法,我很乐意知道! - CervEd
9
Array.includes()不是ES6功能,而是ES7功能 (1) (2)。因此,在ES6中,您可以使用Array.some()。例如,您可以使用以下代码来获取两个数组的交集 let intersection = aArray.filter(a => bArray.some(b => a === b)) - Jari Keinänen
4
我每个月都会回到这个答案,哈哈。 - cup_of
显示剩余15条评论

959

Array.prototype.diff = function(a) {
    return this.filter(function(i) {return a.indexOf(i) < 0;});
};

//////////////
// Examples //
//////////////

const dif1 = [1,2,3,4,5,6].diff( [3,4,5] );  
console.log(dif1); // => [1, 2, 6]


const dif2 = ["test1", "test2","test3","test4","test5","test6"].diff(["test1","test2","test3","test4"]);  
console.log(dif2); // => ["test5", "test6"]

注意:.indexOf().filter()在IE9之前不可用。


55
唯一一个不支持 filter 和 indexOf 函数的浏览器是 IE8,但是IE9支持这两个函数。因此,原文表述并没有错误。 - Bryan Larsen
16
很遗憾,IE7和IE8仍然非常重要,但你可以在MDN网站上找到两个函数的polyfill代码:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf通过IE条件加载“兼容性”下列出的代码,即可支持IE7/8。请注意,不要改变原来的意思。 - 1nfiniti
50
这个解决方案的运行时间复杂度为O(n^2),一个线性的解决方案会更有效率。 - jholloman
79
如果你这样使用函数:[1,2,3].diff([3,4,5]),它会返回[1,2]而不是[1,2,4,5],因此它不能解决原问题,需要注意。 - Bugster
14
@AlinPurcaru 不支持过时的浏览器并不等同于错误。考虑到Netscape 2.0,按照这个定义,这里大部分JS代码都是“错误”的。这种说法很愚蠢。 - NullUserException
显示剩余15条评论

381

这个回答是在2009年写的,所以有点过时,也更适合理解问题。我今天会使用的最佳解决方案是

let difference = arr1.filter(x => !arr2.includes(x));

(感谢其他作者)

我假设你正在比较一个普通的数组。如果不是,请将 for 循环更改为 for .. in 循环。

function arr_diff (a1, a2) {

    var a = [], diff = [];

    for (var i = 0; i < a1.length; i++) {
        a[a1[i]] = true;
    }

    for (var i = 0; i < a2.length; i++) {
        if (a[a2[i]]) {
            delete a[a2[i]];
        } else {
            a[a2[i]] = true;
        }
    }

    for (var k in a) {
        diff.push(k);
    }

    return diff;
}

console.log(arr_diff(['a', 'b'], ['a', 'b', 'c', 'd']));
console.log(arr_diff("abcd", "abcde"));
console.log(arr_diff("zxc", "zxc"));


67
这个方法或许可行,但它需要三次循环才能完成 Array 的 filter 方法可以一行搞定的事情。 - Joshaven Potter
13
只是为了明确起见,这实现了 a1a2 的 _对称差异_,与此处发布的其他答案不同。 - 200_success
36
这不是最好的答案,但我会给它一个慈善赞,以帮助弥补不公正的踩票。只有错误的回答应该被踩票,如果我在处理一项需要浏览无用信息的项目(困难时期会发生这种情况),这个答案甚至可能有所帮助。 - Michael Scheper
3
当执行 var a1 = ['a', 'b'];var a2 = ['a', 'b', 'c', 'd', 'b']; 后,当试图在 a2 数组中使用 filter() 方法过滤出不包含在 a1 数组中的元素时,结果会返回错误答案 ['c', 'd', 'b'] 而不是正确答案 ['c', 'd'] - skbly7
5
最快的方法是最明显的天真解法。我测试了这个线程中提出的所有对称差异方案,获胜者是: `function diff2(a, b) { var i, la = a.length, lb = b.length, res = [];if (!la) return b; else if (!lb) return a; for (i = 0; i < la; i++) { if (b.indexOf(a[i]) === -1) res.push(a[i]); } for (i = 0; i < lb; i++) { if (a.indexOf(b[i]) === -1) res.push(b[i]); } return res;}` - nomæd
显示剩余13条评论

314

使用jQuery是迄今为止获得您要寻找的确切结果最简单的方法:

var diff = $(old_array).not(new_array).get();

diff 现在包含了 old_array 中不在 new_array 中的内容。


4
是的,但只有当它们是对同一对象的引用时才相等({a:1}!={a:1})(证明)。 - Matmarbon
10
这是一个技巧吗?doc将此方法视为DOM元素方法 的一部分,而不是通用的数组帮助程序。因此,现在可能可以这样使用,但在将来的版本中可能无法使用,因为它并非旨在以这种方式使用。尽管如此,如果它能够正式成为通用的数组帮助程序,我会很高兴。 - robsch
2
@robsch 当你在数组中使用.not时,jQuery会使用其内置的实用程序.grep()来过滤数组。我认为这种情况不会改变。 - superphonic
1
@vsync 听起来你想要一个对称差。 - superphonic
这个的时间复杂度是多少?是 O(m x n) 吗,其中 mold_array 的长度,nnew_array 的长度? - Raul
显示剩余5条评论

178

Underscore(或其替代品Lo-Dash)中的difference方法也可以实现此功能:

(R)eturns the values from array that are not present in the other arrays

_.difference([1, 2, 3, 4, 5], [5, 2, 10]);
=> [1, 3, 4]

与任何Underscore函数一样,您还可以以更面向对象的方式使用它:

_([1, 2, 3, 4, 5]).difference([5, 2, 10]);

4
我认为从性能方面考虑,这是一个很好的解决方案,特别是 lodash 和 underscore 一直在争夺最佳实现。此外,它还兼容 IE6。 - mahemoff
4
请注意,此实现不适用于对象数组。有关更多信息,请参见https://dev59.com/YGoy5IYBdhLWcg3wQr1i。 - Gili
1
正如其中一个答案所提到的那样,如果是同一对象,则可以运行,但如果两个对象具有相同的属性,则无法运行。我认为这没关系,因为相等的概念因人而异(例如,在某些应用程序中,它也可以是“id”属性)。但是,如果您可以传递比较测试以进行intersect(),那将是很好的。 - mahemoff
为了后代:Lodash现在有_.differenceBy(),它接受一个回调函数来进行比较;如果你正在比较对象,你可以插入一个比较它们的函数,以任何你需要的方式进行比较。 - SomeCallMeTim
3
注意如果参数的顺序颠倒,它将无法工作。例如:_.difference([5, 2, 10], [1, 2, 3, 4, 5]); 将无法得到差异。 - Russj
计算两个集合的差异时,顺序始终很重要...这是一个关于你想要哪一侧的Venn图的问题。 - mahemoff

98

纯JavaScript

"difference"有两种可能的解释,你可以选择其中一种。比如说你有:

var a1 = ['a', 'b'     ];
var a2 = [     'b', 'c'];
  1. If you want to get ['a'], use this function:

    function difference(a1, a2) {
      var result = [];
      for (var i = 0; i < a1.length; i++) {
        if (a2.indexOf(a1[i]) === -1) {
          result.push(a1[i]);
        }
      }
      return result;
    }
    
  2. If you want to get ['a', 'c'] (all elements contained in either a1 or a2, but not both -- the so-called symmetric difference), use this function:

    function symmetricDifference(a1, a2) {
      var result = [];
      for (var i = 0; i < a1.length; i++) {
        if (a2.indexOf(a1[i]) === -1) {
          result.push(a1[i]);
        }
      }
      for (i = 0; i < a2.length; i++) {
        if (a1.indexOf(a2[i]) === -1) {
          result.push(a2[i]);
        }
      }
      return result;
    }
    

Lodash / Underscore

如果您正在使用lodash,您可以使用_.difference(a1, a2)(第一种情况)或_.xor(a1, a2)(第二种情况)。

如果您正在使用Underscore.js,则可以使用_.difference(a1, a2)函数处理第一种情况。

ES6 Set,用于非常大的数组

上面的代码适用于所有浏览器。然而,对于超过大约10,000项的大型数组,它变得相当慢,因为它具有O(n²)的复杂度。在许多现代浏览器上,我们可以利用ES6的Set对象来加速。当Set可用时,Lodash会自动使用它。如果您没有使用Lodash,请使用以下实现,灵感来自于Axel Rauschmayer的博客文章
function difference(a1, a2) {
  var a2Set = new Set(a2);
  return a1.filter(function(x) { return !a2Set.has(x); });
}

function symmetricDifference(a1, a2) {
  return difference(a1, a2).concat(difference(a2, a1));
}

注意事项

如果您关注-0,+0,NaN或稀疏数组,则所有示例的行为可能会令人惊讶或不明显。(对于大多数用途,这并不重要。)


3
谢谢。你救了我的一天。我需要比较一个三十万大小的数组,而你的"Set"方法完美地解决了我的问题。这应该被接受为答案。 - justadev
1
在这个问题的答案中,我不得不滚动到最底部才找到有人详细说明使用Set来解决这个问题的事实令人惊讶。 - Aaron Beaudoin

76

ES6中更优雅的方法如下所示。

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];

区别

a2.filter(d => !a1.includes(d)) // gives ["c", "d"]

交集

a2.filter(d => a1.includes(d)) // gives ["a", "b"]

排他联合(对称差)

[ ...a2.filter(d => !a1.includes(d)),
  ...a1.filter(d => !a2.includes(d)) ]

它只能单向工作。现在想象一下 a1 = ['a', 'b', 'e']e 将不会被提取。 - imrok
是的,这就是集合理论中差异的工作方式。(a2 - a1)你要找的是(a2-a1) + (a1-a2)。 - ifelse.codes
2
@imrok 我相信这就是你要找的内容[...a2.filter(d => !a1.includes(d)) , ...(a1.filter(d => !a2.includes(d)))] - ifelse.codes

70

为了得到对称差集,需要比较两个数组(或在多个数组的情况下比较所有数组)。

输入图像描述


ES7(ECMAScript 2016)

// diff between just two arrays:
function arrayDiff(a, b) {
    return [
        ...a.filter(x => !b.includes(x)),
        ...b.filter(x => !a.includes(x))
    ];
}

// diff between multiple arrays:
function arrayDiff(...arrays) {
    return [].concat(...arrays.map( (arr, i) => {
        const others = arrays.slice(0);
        others.splice(i, 1);
        const unique = [...new Set([].concat(...others))];
        return arr.filter(x => !unique.includes(x));
    }));
}

ES6(ECMAScript 2015)

// diff between just two arrays:
function arrayDiff(a, b) {
    return [
        ...a.filter(x => b.indexOf(x) === -1),
        ...b.filter(x => a.indexOf(x) === -1)
    ];
}

// diff between multiple arrays:
function arrayDiff(...arrays) {
    return [].concat(...arrays.map( (arr, i) => {
        const others = arrays.slice(0);
        others.splice(i, 1);
        const unique = [...new Set([].concat(...others))];
        return arr.filter(x => unique.indexOf(x) === -1);
    }));
}

ES5 (ECMAScript 5.1)

// diff between just two arrays:
function arrayDiff(a, b) {
    var arrays = Array.prototype.slice.call(arguments);
    var diff = [];

    arrays.forEach(function(arr, i) {
        var other = i === 1 ? a : b;
        arr.forEach(function(x) {
            if (other.indexOf(x) === -1) {
                diff.push(x);
            }
        });
    })

    return diff;
}

// diff between multiple arrays:
function arrayDiff() {
    var arrays = Array.prototype.slice.call(arguments);
    var diff = [];

    arrays.forEach(function(arr, i) {
        var others = arrays.slice(0);
        others.splice(i, 1);
        var otherValues = Array.prototype.concat.apply([], others);
        var unique = otherValues.filter(function (x, j) { 
            return otherValues.indexOf(x) === j; 
        });
        diff = diff.concat(arr.filter(x => unique.indexOf(x) === -1));
    });
    return diff;
}

示例:

// diff between two arrays:
const a = ['a', 'd', 'e'];
const b = ['a', 'b', 'c', 'd'];
arrayDiff(a, b); // (3) ["e", "b", "c"]

// diff between multiple arrays
const a = ['b', 'c', 'd', 'e', 'g'];
const b = ['a', 'b'];
const c = ['a', 'e', 'f'];
arrayDiff(a, b, c); // (4) ["c", "d", "g", "f"]

对象数组之间的区别


function arrayDiffByKey(key, ...arrays) {
    return [].concat(...arrays.map( (arr, i) => {
        const others = arrays.slice(0);
        others.splice(i, 1);
        const unique = [...new Set([].concat(...others))];
        return arr.filter( x =>
            !unique.some(y => x[key] === y[key])
        );
    }));
}

例子:

const a = [{k:1}, {k:2}, {k:3}];
const b = [{k:1}, {k:4}, {k:5}, {k:6}];
const c = [{k:3}, {k:5}, {k:7}];
arrayDiffByKey('k', a, b, c); // (4) [{k:2}, {k:4}, {k:6}, {k:7}]

53
你可以在这种情况下使用一个Set。它针对这种操作进行了优化(并集、交集、差集)。
确保它适用于你的情况,因为它不允许重复项。
var a = new JS.Set([1,2,3,4,5,6,7,8,9]);
var b = new JS.Set([2,4,6,8]);

a.difference(b)
// -> Set{1,3,5,7,9}

4
看起来那是一个不错的图书馆!可惜你不能只下载“Set”函数而必须要下载其他所有内容… - Blixt
@Blixt 我相信你可以下载所有文件,然后只需包含set.js文件。 - Samuel Carrijo
在Google Closure中也实现了Set。http://closure-library.googlecode.com/svn/docs/class_goog_structs_Set.html - Ben Flynn
2
哇,已经一年了?https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set - Loupax
6
很遗憾,内置的 ES Set 没有这个方便的“difference”方法。 - Charles Wood

44

一行代码

const unique = (a) => [...new Set(a)];
const uniqueBy = (x,f)=>Object.values(x.reduce((a,b)=>((a[f(b)]=b),a),{}));
const intersection = (a, b) => a.filter((v) => b.includes(v));
const diff = (a, b) => a.filter((v) => !b.includes(v));
const symDiff = (a, b) => diff(a, b).concat(diff(b, a));
const union = (a, b) => diff(a, b).concat(b);

const a = unique([1, 2, 3, 4, 5, 5]);
console.log(a);
const b = [4, 5, 6, 7, 8];

console.log(intersection(a, b), diff(a, b), symDiff(a, b), union(a, b));

console.log(uniqueBy(
  [
{ id: 1, name: "abc" },
{ id: 2, name: "xyz" },
{ id: 1, name: "abc" },
  ],
  (v) => v.id
));

const intersectionBy = (a, b, f) => a.filter((v) => b.some((u) => f(v, u)));

console.log(intersectionBy(
 [
  { id: 1, name: "abc" },
  { id: 2, name: "xyz" },
 ],
 [
  { id: 1, name: "abc" },
  { id: 3, name: "pqr" },
 ],
 (v, u) => v.id === u.id
));

const diffBy = (a, b, f) => a.filter((v) => !b.some((u) => f(v, u)));

console.log(diffBy(
 [
  { id: 1, name: "abc" },
  { id: 2, name: "xyz" },
 ],
 [
  { id: 1, name: "abc" },
  { id: 3, name: "pqr" },
 ],
 (v, u) => v.id === u.id
));

TypeScript

playground link

const unique = <T>(array: T[]) => [...new Set(array)];


const intersection = <T>(array1: T[], array2: T[]) =>
  array1.filter((v) => array2.includes(v));


const diff = <T>(array1: T[], array2: T[]) =>
  array1.filter((v) => !array2.includes(v));


const symDiff = <T>(array1: T[], array2: T[]) =>
  diff(array1, array2).concat(diff(array2, array1));


const union = <T>(array1: T[], array2: T[]) =>
  diff(array1, array2).concat(array2);


const intersectionBy = <T>(
  array1: T[],
  array2: T[],
  predicate: (array1Value: T, array2Value: T) => boolean
) => array1.filter((v) => array2.some((u) => predicate(v, u)));


const diffBy = <T>(
  array1: T[],
  array2: T[],
  predicate: (array1Value: T, array2Value: T) => boolean
) => array1.filter((v) => !array2.some((u) => predicate(v, u)));


const uniqueBy = <T>(
  array: T[],
  predicate: (v: T, i: number, a: T[]) => string
) =>
  Object.values(
    array.reduce((acc, value, index) => {
      acc[predicate(value, index, array)] = value;
      return acc;
    }, {} as { [key: string]: T })
  );

有没有 uniqueBy 的 TS 版本? - rantao
1
@rantao,准备好了。 - nkitku

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