如何在JavaScript中比较数组?

1525

我想比较两个数组……最好是高效地进行。不需要花哨的操作,只需在它们相同时返回true,否则返回false。不出所料,比较运算符似乎无法胜任。

var a1 = [1,2,3];
var a2 = [1,2,3];
console.log(a1==a2);    // Returns false
console.log(JSON.stringify(a1)==JSON.stringify(a2));    // Returns true

JSON对每个数组进行编码,但是否有更快或“更好”的方法,可以在不必迭代每个值的情况下简单地比较数组?


11
你可以先比较它们的长度,如果长度相等就比较每个值。 - TJHeuvel
75
你认为两个数组相等的条件是什么?元素相同?元素顺序相同?只有当数组的元素可以序列化为JSON时,将其编码为JSON才起作用。如果数组中可能包含对象,你会深入多少层?什么情况下两个对象才是“相等”的? - Felix Kling
92
@FelixKling,“平等”这个概念确实是一个微妙的话题,但对于从高级语言转入JavaScript的人来说,像([] == []) == false这样的愚蠢行为是不可原谅的。 - Alex D
9
看起来数组使用引用相等性,这是你所期望的。如果不能这样做的话会非常糟糕。 - JonnyRaa
7
@AlexD 我有点想不到哪种语言不会发生这种情况。在C++中,您将比较两个指针-错误。在Java中,您所做的与javascript相同。在PHP中,某些后台操作会循环遍历数组-你认为PHP是高级语言吗? - Tomáš Zato
显示剩余10条评论
56个回答

1067

要比较数组,需要循环遍历它们并逐个比较每个值:

比较数组:

// Warn if overriding existing method
if(Array.prototype.equals)
    console.warn("Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code.");
// attach the .equals method to Array's prototype to call it on any array
Array.prototype.equals = function (array) {
    // if the other array is a falsy value, return
    if (!array)
        return false;
    // if the argument is the same array, we can be sure the contents are same as well
    if(array === this)
        return true;
    // compare lengths - can save a lot of time 
    if (this.length != array.length)
        return false;

    for (var i = 0, l=this.length; i < l; i++) {
        // Check if we have nested arrays
        if (this[i] instanceof Array && array[i] instanceof Array) {
            // recurse into the nested arrays
            if (!this[i].equals(array[i]))
                return false;       
        }           
        else if (this[i] != array[i]) { 
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;   
        }           
    }       
    return true;
}
// Hide method from for-in loops
Object.defineProperty(Array.prototype, "equals", {enumerable: false});

使用方法:

[1, 2, [3, 4]].equals([1, 2, [3, 2]]) === false;
[1, "2,3"].equals([1, 2, 3]) === false;
[1, 2, [3, 4]].equals([1, 2, [3, 4]]) === true;
[1, 2, 1, 2].equals([1, 2, 1, 2]) === true;

你可能会说:“但是比较字符串会快得多 - 没有循环...”,那么你应该注意,确实存在循环。首先是递归循环将数组转换为字符串,其次是比较两个字符串的循环。因此,这种方法比使用字符串更快

我认为大量的数据应该始终存储在数组中,而不是对象中。然而,如果你使用对象,它们也可以进行部分比较。
下面是具体方法:

比较对象:

如上所述,即使它们在某一时刻包含相同的数据,两个对象实例也永远不会相等:

({a:1, foo:"bar", numberOfTheBeast: 666}) == ({a:1, foo:"bar", numberOfTheBeast: 666})  //false

这是有原因的,因为对象内可能存在私有变量

然而,如果你只是使用对象结构来包含数据,比较仍然是可行的:

Object.prototype.equals = function(object2) {
    //For the first loop, we only check for types
    for (propName in this) {
        //Check for inherited methods and properties - like .equals itself
        //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
        //Return false if the return value is different
        if (this.hasOwnProperty(propName) != object2.hasOwnProperty(propName)) {
            return false;
        }
        //Check instance type
        else if (typeof this[propName] != typeof object2[propName]) {
            //Different types => not equal
            return false;
        }
    }
    //Now a deeper check using other objects property names
    for(propName in object2) {
        //We must check instances anyway, there may be a property that only exists in object2
            //I wonder, if remembering the checked values from the first loop would be faster or not 
        if (this.hasOwnProperty(propName) != object2.hasOwnProperty(propName)) {
            return false;
        }
        else if (typeof this[propName] != typeof object2[propName]) {
            return false;
        }
        //If the property is inherited, do not check any more (it must be equa if both objects inherit it)
        if(!this.hasOwnProperty(propName))
          continue;
        
        //Now the detail check and recursion
        
        //This returns the script back to the array comparing
        /**REQUIRES Array.equals**/
        if (this[propName] instanceof Array && object2[propName] instanceof Array) {
                   // recurse into the nested arrays
           if (!this[propName].equals(object2[propName]))
                        return false;
        }
        else if (this[propName] instanceof Object && object2[propName] instanceof Object) {
                   // recurse into another objects
                   //console.log("Recursing to compare ", this[propName],"with",object2[propName], " both named \""+propName+"\"");
           if (!this[propName].equals(object2[propName]))
                        return false;
        }
        //Normal value comparison for strings and numbers
        else if(this[propName] != object2[propName]) {
           return false;
        }
    }
    //If everything passed, let's say YES
    return true;
}  

请记住,这个方法是用来比较 JSON 类似数据的,而不是类实例或其它东西。如果你想比较更复杂的对象,请看这个答案和它超长的函数
为了让它能够与 Array.equals 一起工作,你必须稍微编辑原始函数:

...
    // Check if we have nested arrays
    if (this[i] instanceof Array && array[i] instanceof Array) {
        // recurse into the nested arrays
        if (!this[i].equals(array[i]))
            return false;
    }
    /**REQUIRES OBJECT COMPARE**/
    else if (this[i] instanceof Object && array[i] instanceof Object) {
        // recurse into another objects
        //console.log("Recursing to compare ", this[propName],"with",object2[propName], " both named \""+propName+"\"");
        if (!this[i].equals(array[i]))
            return false;
        }
    else if (this[i] != array[i]) {
...

我做了一个小测试工具,用于两个函数

Bonus: 使用indexOfcontains处理嵌套数组

Samy Bencherif准备了有用的函数,用于在嵌套数组中搜索特定对象,可以在这里找到: https://jsfiddle.net/SamyBencherif/8352y6yw/


36
如果你想要进行严格的比较,请使用this[i] !== array[i]而不是!= - Tim S.
42
你的方法应该被称为 equals 而不是 compare。至少在 .NET 中,compare 通常会返回一个有符号整数,表示哪个对象大于另一个对象。请参见:Comparer.Compare - Oliver
17
这不仅是正确的做法,而且更加高效。我为本问题中提出的所有方法准备了一个快速的jsperf脚本,链接为:http://jsperf.com/comparing-arrays2 - Tolga E
135
改变内置类型的原型绝对不是“正确的方法”。 - Jasper
43
此外,这不是关于重写是否容易的问题,而是关于答案不应该推荐被认为是不良实践的东西(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain#Bad_practice.3A_Extension_of_native_prototypes)并且绝对**不应该**在标题“正确的方法”下面执行下面的操作。 - Jasper
显示剩余48条评论

626

虽然这仅适用于标量数组(见下面的注释),但代码很短:

array1.length === array2.length && array1.every(function(value, index) { return value === array2[index]})

与上面相同,但使用ECMAScript 6 / CoffeeScript / TypeScript的箭头函数:

array1.length === array2.length && array1.every((value, index) => value === array2[index])

(注意: 这里的“标量”指可直接使用 === 进行比较的值。因此:数字、字符串、按引用传递的对象、按引用传递的函数。有关比较运算符的更多信息,请参见MDN参考文档。)

更新

从评论中得出,对数组进行排序并进行比较可能会得到准确的结果:

const array2Sorted = array2.slice().sort();
array1.length === array2.length && array1.slice().sort().every(function(value, index) {
    return value === array2Sorted[index];
});
示例:
array1 = [2,3,1,4];
array2 = [1,2,3,4];

那么上面的代码将返回true


25
我喜欢这个,但读者应该意识到这仅适用于已排序的数组。 - Ellen Spertus
24
它适用于任何类型的数组,无论是否排序。@espertus - Michał Miszczyszyn
53
好的,明白了。这个函数的作用是比较两个数组,不管它们是否排序,只要它们的相邻元素相等即可。 - Michał Miszczyszyn
33
确实,如果两个数组中的元素顺序不完全相同,它将不会返回true。然而,相等性检查的目的并不是检查它们是否包含相同的元素,而是检查它们是否按相同的顺序具有相同的元素。 - Quentin Roy
12
如果你想检查两个数组是否相等,包含相同的未排序项(但不重复使用),你可以使用a1.length==a2.length && a1.every((v,i)=>a2.includes(v))var a1 =[1,2,3], a2 = [3,2,1];var a1 =[1,3,3], a2 = [1,1,3];将不能如预期般工作)。 - mems
显示剩余12条评论

293

我喜欢在需要处理数组或对象的重度编码项目中使用Underscore库… 在Underscore和Lodash中,无论是比较数组还是对象,它看起来都像这样:

_.isEqual(array1, array2)   // returns a boolean
_.isEqual(object1, object2) // returns a boolean

39
请注意顺序很重要:_.isEqual([1,2,3], [2,1,3]) => false - Vitalii Alekask
4
如果您只想使用isEqual功能,可以始终使用lodash.isequal模块。 - hellatan
10
如果顺序不重要的话,您可以尝试使用 _.difference()。 - Ronan Quillevere
16
如果顺序无关紧要,我们可以在检查之前对数组进行排序。_.isEqual([1,2,3].sort(), [2,1,3].sort()) => true - filype
在 React.js 中抛出异常:'_' 未定义。 - Mike D3ViD Tyson
迄今为止对我来说最好的答案,因为我不想重复造轮子... - rubebop

236

我认为使用JSON stringify这是最简单的方法,并且在某些情况下可能是最好的解决方案:

JSON.stringify(a1) === JSON.stringify(a2);

这将对象a1a2转换为字符串,以便它们可以进行比较。在大多数情况下,顺序很重要,因为可以使用上面其中一个答案中显示的排序算法来对对象进行排序。

请注意,您现在比较的是对象的字符串表示形式,可能不完全符合您的要求。


5
@PardeepJain,这是因为在ECMAScript中,对象的默认相等运算符会在它们引用同一内存位置时返回true。尝试使用var x = y = []; //现在相等运算符将返回true。 - radtek
13
请注意,JSON stringify函数并不快速。使用较大的数组时,肯定会引入延迟。 - Lukas Liesis
21
问题明确要求是否有比使用JSON.stringify更好/更快的方法。 - Don Hatch
2
它更详细地解释了为什么这可能是某些情况下的好解决方案。 - radtek
2
说实话,一开始我没有注意到原问题提到了 JSON.stringify() - 对于简单的用例来说,它感觉是最容易的选择... - Mars Robertson
显示剩余5条评论

178

以原问题的精神为基础:

我想比较两个数组... 最好是 高效地没有花哨的,如果它们相同则返回 true,否则返回 false。

我已经对这里提出的一些更简单的建议进行了性能测试,以下是结果(从快到慢):

while (67%) by Tim Down

var i = a1.length;
while (i--) {
    if (a1[i] !== a2[i]) return false;
}
return true

每个(69%)由user2782196发布

a1.every((v,i)=> v === a2[i]);

reduce(74%)由DEIs完成

a1.reduce((a, b) => a && a2.includes(b), true);

join & toString (78%) 由 Gaizka Allende 和 vivek 创作

a1.join('') === a2.join('');

a1.toString() === a2.toString();

half toString (90%) 由 Victor Palomo 完成

a1 == a2.toString();

stringify (100%) 由radtek创建

JSON.stringify(a1) === JSON.stringify(a2);

请注意,下面的示例假定数组是已排序的单维数组。为了进行公共基准测试,已删除了.length比较(将a1.length === a2.length添加到任何建议中,您将获得约10%的性能提升)。选择最适合您的解决方案,了解每个解决方案的速度和限制即可。
第三方编辑:
我觉得jsbench现在正在从快速(100%)到较慢(例如11%)进行排序。代码示例包含arrEvery()和一个减速版本。

function arrEvery(a1,a2) 
{
    // user2782196: https://dev59.com/V2sz5IYBdhLWcg3wiISe#19746771
    return a1.every((v,i)=> v === a2[i]);
}

// slowed down
function arrEveryWithSlowDown(a1,a2) 
{
    setTimeout(() => {
        console.log("Slow down pal!");
    }, 1500);
    return a1.every((v,i)=> v === a2[i]);
}

arrEvery 的缓慢版本具有较低的百分比。括号中的数字可能是运行的周期数/操作数。

jsbench.ch result array comparison


如果你增加了数组的大小,这些数字就不适用了(特别是缩减方法)。请尝试使用 Array.from({length: 1000}).map((a,v)=> ${v}.padStart(10,2)); - Narayon
应该在a1a2连接之前使用sort()。例如,a1.sort().join("")===a2.sort().join("") - KR Tirtho
5
join('')很危险,因为['foo','bar'].join('')== ['foobar'] .join('')。我更喜欢a1 ==''+ a2 - Cees Timmerman
16
这些%数字究竟是什么意思? - The Human Cat
3
@TheHumanCat 我很确定最慢的实现代表了100%,而其他实现则代表该时间的一小部分。因此使用while所需的时间将是使用stringify所需时间的67%。这与面包师百分比有些相似... - Shawn
显示剩余7条评论

89

实用方法

我认为说某个特定的实现方式是“正确的”(“correct”)只是因为它与“错误”的解决方案相比而言是“正确的”,这种说法是不正确的。 Tomáš的解决方案明显比基于字符串比较数组要好,但这并不意味着它从客观上来说就是“正确的”。到底什么才是“正确”的呢?是最快的吗?最灵活的吗?易于理解吗?最快速调试的吗?使用的操作最少吗?它会有任何副作用吗?没有一种解决方案可以拥有所有优点。

Tomáš可能会说他的解决方案很快,但我也会说它过于复杂了。它试图成为一个适用于所有数组,包括嵌套数组在内的多合一解决方案。实际上,它甚至接受更多不同于数组的输入,并仍然尝试给出一个“有效”的答案。


泛型提供可重用性

我的答案将以一种不同的方式来解决这个问题。我将从一个通用的arrayCompare过程开始,该过程只关心数组的遍历。从那里,我们将构建我们的其他基本比较函数,如arrayEqualarrayDeepEqual等。

// arrayCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayCompare = f => ([x,...xs]) => ([y,...ys]) =>
  x === undefined && y === undefined
    ? true
    : Boolean (f (x) (y)) && arrayCompare (f) (xs) (ys)
在我看来,最好的代码不需要注释,这段代码也不例外。这里发生的事情非常少,你几乎不需要花费任何精力就可以理解这个过程的行为。当然,一些ES6语法现在可能对你来说似曾相识,但那只是因为ES6相对较新。
如其类型所示,arrayCompare函数接受比较函数f和两个输入数组xsys。在大部分情况下,我们只是对输入数组中的每个元素调用f(x)(y)。如果用户定义的f返回false,我们会提前返回false——这要感谢&&的短路评估。所以是的,这意味着比较器可以在不必要时停止迭代并防止循环遍历其余的输入数组。

严格比较

接下来,使用我们的arrayCompare函数,我们可以轻松创建其他可能需要的函数。我们将从基本的arrayEqual开始...
// equal :: a -> a -> Bool
const equal = x => y =>
  x === y // notice: triple equal

// arrayEqual :: [a] -> [a] -> Bool
const arrayEqual =
  arrayCompare (equal)

const xs = [1,2,3]
const ys = [1,2,3]
console.log (arrayEqual (xs) (ys))      //=> true
// (1 === 1) && (2 === 2) && (3 === 3)  //=> true

const zs = ['1','2','3']
console.log (arrayEqual (xs) (zs))      //=> false
// (1 === '1')                          //=> false

如此简单。可以使用arrayCompare和一个比较函数定义arrayEqual,该函数使用===(用于严格相等性)将ab进行比较。

请注意,我们还将equal定义为自己的函数。这突出了arrayCompare作为高阶函数在另一个数据类型(数组)的上下文中利用我们的一阶比较器的角色。


松散比较

我们也可以使用==来定义arrayLooseEqual,这样当比较1(数字)和'1'(字符串)时,结果将是true……

// looseEqual :: a -> a -> Bool
const looseEqual = x => y =>
  x == y // notice: double equal

// arrayLooseEqual :: [a] -> [a] -> Bool
const arrayLooseEqual =
  arrayCompare (looseEqual)

const xs = [1,2,3]
const ys = ['1','2','3']
console.log (arrayLooseEqual (xs) (ys))    //=> true
// (1 == '1') && (2 == '2') && (3 == '3')  //=> true

深层次比较(递归)

您可能已经注意到这只是浅层次的比较。Tomáš的解决方案肯定是“正确的方式™”,因为它进行了隐式的深度比较,对吗?

好吧,我们的arrayCompare过程足够灵活,可以使用一种使深度相等测试变得轻松的方式…

// isArray :: a -> Bool
const isArray =
  Array.isArray

// arrayDeepCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayDeepCompare = f =>
  arrayCompare (a => b =>
    isArray (a) && isArray (b)
      ? arrayDeepCompare (f) (a) (b)
      : f (a) (b))

const xs = [1,[2,[3]]]
const ys = [1,[2,['3']]]
console.log (arrayDeepCompare (equal) (xs) (ys)) //=> false
// (1 === 1) && (2 === 2) && (3 === '3')         //=> false

console.log (arrayDeepCompare (looseEqual) (xs) (ys)) //=> true
// (1 == 1) && (2 == 2) && (3 == '3')                 //=> true

简单来说,我们使用另一个高阶函数构建了一个深度比较器。 这次,我们使用自定义比较器包装 arrayCompare,以检查 ab 是否为数组。 如果是,则重新应用 arrayDeepCompare,否则将 ab 与用户指定的比较器(f)进行比较。 这使我们可以将深层比较行为与我们实际比较单个元素的方式分开。 例如,就像上面的示例所示,我们可以使用 equallooseEqual 或任何其他我们制作的比较器进行深度比较。

由于 arrayDeepCompare 是柯里化的,因此我们也可以像之前的示例一样部分应用它。

// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
  arrayDeepCompare (equal)

// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
  arrayDeepCompare (looseEqual)

对我来说,这已经是对Tomáš解决方案的明显改进,因为我可以根据需要明确地选择我的数组进行浅层比较或深层比较。


对象比较(示例)

如果你有一个对象数组或类似的东西,该怎么办?也许你想要将这些数组视为“相等”,如果每个对象具有相同的id值…

// idEqual :: {id: Number} -> {id: Number} -> Bool
const idEqual = x => y =>
  x.id !== undefined && x.id === y.id

// arrayIdEqual :: [a] -> [a] -> Bool
const arrayIdEqual =
  arrayCompare (idEqual)

const xs = [{id:1}, {id:2}]
const ys = [{id:1}, {id:2}]
console.log (arrayIdEqual (xs) (ys)) //=> true
// (1 === 1) && (2 === 2)            //=> true

const zs = [{id:1}, {id:6}]
console.log (arrayIdEqual (xs) (zs)) //=> false
// (1 === 1) && (2 === 6)            //=> false

就这么简单。这里我使用了原始的JS对象,但是这种比较器可以适用于 任何 对象类型,甚至是您自定义的对象。Tomáš的解决方案需要完全重新设计以支持这种相等性测试。

包含对象的深层数组? 没问题。我们构建了高度通用的函数,因此它们将在各种用例中起作用。

const xs = [{id:1}, [{id:2}]]
const ys = [{id:1}, [{id:2}]]
console.log (arrayCompare (idEqual) (xs) (ys))     //=> false
console.log (arrayDeepCompare (idEqual) (xs) (ys)) //=> true

任意比较(示例)

或者,如果你想进行其他种类的完全任意比较呢?也许我想知道每个x是否都大于每个y……


// gt :: Number -> Number -> Bool
const gt = x => y =>
  x > y

// arrayGt :: [a] -> [a] -> Bool
const arrayGt = arrayCompare (gt)

const xs = [5,10,20]
const ys = [2,4,8]
console.log (arrayGt (xs) (ys))     //=> true
// (5 > 2) && (10 > 4) && (20 > 8)  //=> true

const zs = [6,12,24]
console.log (arrayGt (xs) (zs))     //=> false
// (5 > 6)                          //=> false

越简单越好

我们可以看到,我们确实是用更少的代码做更多的事情。 arrayCompare本身并不复杂,我们制作的每个自定义比较器都有非常简单的实现。

轻松地,我们可以精确定义我们希望如何比较两个数组 - 浅层、深层、严格、松散、一些对象属性或某些任意计算,或这些的任何组合,全部使用一个过程arrayCompare 。甚至可以想象一个RegExp比较器!我知道孩子们喜欢那些正则表达式…

它是最快的吗?不是。但它可能也不需要。如果速度是衡量我们代码质量的唯一指标,很多真正优秀的代码都会被丢弃 - 这就是为什么我称这种方法为“实用方式”的原因。或者更公平地说,是“一种”实用方式。这个描述适用于此答案,因为我并不是说这个答案只有在与其他答案相比时才是实用的;这是客观的事实。我们用很少的代码达到了高度的实用性,而这些代码很容易理解。没有其他代码可以说我们没有赢得这个称号。

这是否使它成为“正确”的解决方案?这取决于。没有人能为你做这个决定;只有你知道你的需求。在几乎所有情况下,我更看重直接、实用和多功能的代码而不是聪明和快速的代码。你重视什么可能不同,所以选择适合你的。


编辑

我的旧答案更侧重于将arrayEqual分解为小程序。这是一个有趣的练习,但并不是处理这个问题的最佳(最实用)方式。如果您感兴趣,可以查看此修订历史记录。


14
“最好的代码不需要注释”,但我得说这段代码可能需要更多的注释和/或不同的名称——“compare”这个名称有点模糊。如果我没理解错,你的“compare”本质上是一个柯里化递归的“every”,我想是这样的。还是一个柯里化递归的“some”?嗯,这需要比必要的思考更多。也许一个更好的名称是“arraysEquivalent”,利用“等价关系”的标准术语。甚至更清晰的(至少对我来说)是“recursivelyEquivalent”。 - Don Hatch
1
@DonHatch 感谢您提供回复的机会。您所说的“比较”,是指 arrayCompare 吗?是的,该函数是柯里化的,但它与 someevery 不同。arrayCompare 需要一个比较器和两个数组进行比较。我选择了一个特别通用的名称,因为我们可以使用任意函数来比较数组。该函数是柯里化的,因此可以专门用于创建新的数组比较函数(例如,arrayEqual)。您能否建议更好的名称?您认为哪些方面需要额外的注释或解释?我很乐意讨论 ^_^ - Mulan
1
不确定我的观点是否清晰--但我的观点是,我不认为你的函数真的旨在接受一个任意函数--它旨在接受一个等价关系,并返回一个等价关系。这很重要--如果给定其他类型的任意二元函数(如我提到的那些),即使是人们经常称之为“比较”的函数,它也不会做出任何明智的事情(我认为)。因此,我认为用“等价”代替“比较”中的名称会有所帮助。 - Don Hatch
1
@ftor,作者:非常有帮助的答案,干得好,+1。反馈:您提倡简单,但对于许多开发人员来说,在一行中使用三个箭头的表达式并不简单或易于理解。例如:f=>([x,...xs])=>([y,...ys])=>。我经常使用它,但仍然不得不在脑海中分解它,而不是“只看它”。第二点ftor是正确的,使用every。即使权衡您的原因,从您的角度来看,似乎更好不仅对我而言,而且在尝试推断您的设计哲学时也是如此。 - whitneyland
2
我知道这是一个学习的地方,但我在这里做出一个假设,即学习函数式编程风格的平均程序员可以将任何柯里化函数转换为非柯里化函数。我的回答并没有暗示这种风格应该在你自己的程序中使用 - 写成非柯里化形式,使用自己的缩进规则,以任何你想要的方式写都可以 - 我用我认为最能表达程序的风格来写我的答案。我也喜欢邀请其他人挑战我们在语法上表达程序的方式。 - Mulan
显示剩余21条评论

67

你所说的“相同”意思不明确。例如,下面的数组ab是否相同(注意嵌套数组)?

var a = ["foo", ["bar"]], b = ["foo", ["bar"]];

这是一个优化的数组比较函数,它逐个比较每个数组对应的元素,使用严格相等运算符,并不会对自身为数组的数组元素进行递归比较。对于上述示例,arraysIdentical(a, b)将返回false。它适用于一般情况,而基于 JSON 和 join() 的解决方案则不具备普适性:

function arraysIdentical(a, b) {
    var i = a.length;
    if (i != b.length) return false;
    while (i--) {
        if (a[i] !== b[i]) return false;
    }
    return true;
};

@ASDF:从问题中无法确定“相同”是什么意思。显然,这个答案只进行了浅层次的检查。我会添加一条注释。 - Tim Down
这个无法处理数组:Identical([1, 2, [3, 2]],[1, 2, [3, 2]]); - Gopinath Shiva
4
如果你期望返回 true,那么它只会失败。这个答案解释了它不会返回 true。如果你需要比较嵌套的数组,你可以轻松地添加递归检查。 - Tim Down

30

在Tomáš Zato的回答基础上,我同意仅仅迭代遍历数组是最快的方法。此外(就像其他人已经说过的那样),这个函数应该被称为equals/equal,而不是compare。考虑到这一点,我修改了这个函数,以处理比较相似数组 - 即它们具有相同的元素,但顺序不同 - 供个人使用,并想让大家都看到。

Array.prototype.equals = function (array, strict) {
    if (!array)
        return false;

    if (arguments.length == 1)
        strict = true;

    if (this.length != array.length)
        return false;

    for (var i = 0; i < this.length; i++) {
        if (this[i] instanceof Array && array[i] instanceof Array) {
            if (!this[i].equals(array[i], strict))
                return false;
        }
        else if (strict && this[i] != array[i]) {
            return false;
        }
        else if (!strict) {
            return this.sort().equals(array.sort(), true);
        }
    }
    return true;
}

此函数需要一个额外的参数strict,默认为true。严格模式(strict mode)定义了数组内容以及顺序是否完全相等,或者仅包含相同的内容。

例子:

var arr1 = [1, 2, 3, 4];
var arr2 = [2, 1, 4, 3];  // Loosely equal to 1
var arr3 = [2, 2, 3, 4];  // Not equal to 1
var arr4 = [1, 2, 3, 4];  // Strictly equal to 1

arr1.equals(arr2);         // false
arr1.equals(arr2, false);  // true
arr1.equals(arr3);         // false
arr1.equals(arr3, false);  // false
arr1.equals(arr4);         // true
arr1.equals(arr4, false);  // true

我也用这个函数编写了一个快速的jsfiddle示例:
http://jsfiddle.net/Roundaround/DLkxX/


17

与JSON.encode类似的方法是使用join()。

function checkArrays( arrA, arrB ){

    //check if lengths are different
    if(arrA.length !== arrB.length) return false;


    //slice so we do not effect the original
    //sort makes sure they are in order
    //join makes it a string so we can do a string compare
    var cA = arrA.slice().sort().join(","); 
    var cB = arrB.slice().sort().join(",");

    return cA===cB;

}

var a = [1,2,3,4,5];
var b = [5,4,3,2,1];
var c = [1,2,3,4];
var d = [1,2,3,4,6];
var e = ["1","2","3","4","5"];  //will return true

console.log( checkArrays(a,b) );  //true
console.log( checkArrays(a,c) );  //false
console.log( checkArrays(a,d) );  //false
console.log( checkArrays(a,e) );  //true

唯一的问题是,如果您关心最后一个比较测试的类型。

如果您关心类型,则必须循环。

function checkArrays( arrA, arrB ){

    //check if lengths are different
    if(arrA.length !== arrB.length) return false;

    //slice so we do not effect the orginal
    //sort makes sure they are in order
    var cA = arrA.slice().sort(); 
    var cB = arrB.slice().sort();

    for(var i=0;i<cA.length;i++){
         if(cA[i]!==cB[i]) return false;
    }

    return true;

}

var a = [1,2,3,4,5];
var b = [5,4,3,2,1];
var c = [1,2,3,4];
var d = [1,2,3,4,6];
var e = ["1","2","3","4","5"];

console.log( checkArrays(a,b) );  //true
console.log( checkArrays(a,c) );  //false
console.log( checkArrays(a,d) );  //false
console.log( checkArrays(a,e) );  //false

如果顺序应该保持不变,那么它只是一个循环,不需要排序。

function checkArrays( arrA, arrB ){

    //check if lengths are different
    if(arrA.length !== arrB.length) return false;


    for(var i=0;i<arrA.length;i++){
         if(arrA[i]!==arrB[i]) return false;
    }

    return true;

}

var a = [1,2,3,4,5];
var b = [5,4,3,2,1];
var c = [1,2,3,4];
var d = [1,2,3,4,6];
var e = ["1","2","3","4","5"];

console.log( checkArrays(a,a) );  //true
console.log( checkArrays(a,b) );  //false
console.log( checkArrays(a,c) );  //false
console.log( checkArrays(a,d) );  //false
console.log( checkArrays(a,e) );  //false

3
这只适用于特定的数组,对于大型数组会非常慢。 - Tomáš Zato
2
生成JSON正在循环,只是您(或看起来是这样)不知道。除了循环之外,生成JSON还需要更多的内存-它在比较之前创建了2个所述数组的字符串表示。 downwote函数被实现为将答案从最好到最差排序。我认为您的答案不是一个好答案,所以我对其进行了downvote。 - Tomáš Zato
2
抱歉,我刚才说错了,应该是使用.join()而不是JSON。如果你把第二种解决方案作为主要方案(尽管它对多维数组无效,但更好),我就不会这样评价你了。到目前为止,我已经给所有将数组转换为字符串的答案点了踩。同时,我也点赞了那些使用正确方法的答案,以防你需要知道。这意味着@Tim Down的答案和Bireys的答案。 - Tomáš Zato
6
第一个版本失败了:checkArrays([1,2,3] , ["1,2",3]) == true,这很不可能是你想要发生的! - Doin
2
@epascarello:是的,你可以这样做,但是(除了你建议的非常长的分隔符的低效率之外),这意味着会有边缘情况(数组恰好包含一个带有您的分隔符的字符串),导致checkArrays()函数表现不良。如果您知道数组内容的某些信息(因此可以选择确保不在数组项中的分隔符),则可能不会出现问题,但是如果您尝试编写通用数组比较函数,则像这样使用join()会使其微妙地出错! - Doin
显示剩余8条评论

15
尽管这个问题有很多答案,但我认为以下答案会有所帮助:
const newArray = [ ...new Set( [...arr1, ...arr2] ) ]

问题中没有说明数组的结构将是什么样子的,所以如果您确定您的数组中不会有嵌套数组和对象(我遇到过这种情况,这就是我写这篇答案的原因),那么上面的代码将起作用。

发生的事情是我们使用展开操作符(...)将两个数组连接起来,然后使用Set来消除任何重复项。一旦你有了这个,你可以比较它们的大小,如果三个数组的大小相同,你就可以继续了。

这个答案也忽略了元素的顺序,正如我所说,恰巧发生在我身上,所以也许处于同样境地的人也会来到这里(就像我一样)。


Edit1.

回答Dmitry Grinko的问题:“为什么你在这里使用展开操作符(...)- ...new Set?它不能工作”

考虑以下代码:

const arr1 = [ 'a', 'b' ]
const arr2 = [ 'a', 'b', 'c' ]
const newArray = [ new Set( [...arr1, ...arr2] ) ]
console.log(newArray)

您将获得

[ Set { 'a', 'b', 'c' } ]

为了使用这个值,您需要使用一些Set属性(请参见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)。 另一方面,当您使用此代码:

const arr1 = [ 'a', 'b' ]
const arr2 = [ 'a', 'b', 'c' ]
const newArray = [ ...new Set( [...arr1, ...arr2] ) ]
console.log(newArray)

你将会得到

[ 'a', 'b', 'c' ]

这就是区别所在:前者会给我一个集合(Set),它也可以工作,因为我可以得到该集合的大小,但后者却直接给我所需的数组,更加直接解决问题。


为什么你在这里使用了扩展运算符(...)和 new Set?它不起作用。 - Dmitry Grinko
Dmitry Grinko,我相信我在我的Edit1中回答了你的问题。但是我不确定你所说的“它不起作用”是什么意思,因为这两个答案都可以帮助你。 - Jeferson Euclides
1
[1, 2, 2, 3], [1, 2, 2, 3] < 这不会导致失败吗?结果集的长度将与输入不同。 - Leo Lei
@LeoLei,你说得对,正如帖子中所解释的那样,这非常特定于某些情况。我在比较数据库响应时使用它,我知道我不会遇到类似你提到的情况。 - Jeferson Euclides
1
不错!我正在寻找类似的东西。 这应该有更多的投票。 点赞。 - EigenFool

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