我想比较两个数组。。。理想地、有效地。没有什么稀奇古怪的,如果它们是相同的,那就是真的,如果不是,那就是假的。毫不奇怪,比较运算符似乎不起作用。

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对每个数组进行编码,但是否有一种更快或“更好”的方法来简单地比较数组而不必遍历每个值?


当前回答

虽然这个问题的最佳答案是正确和良好的,但提供的代码可能需要一些改进。

下面是我自己比较数组和对象的代码。代码简短而简单:

Array.prototype.equals = function(otherArray) {
  if (!otherArray || this.length != otherArray.length) return false;
  return this.reduce(function(equal, item, index) {
    var otherItem = otherArray[index];
    var itemType = typeof item, otherItemType = typeof otherItem;
    if (itemType !== otherItemType) return false;
    return equal && (itemType === "object" ? item.equals(otherItem) : item === otherItem);
  }, true);
};

if(!Object.prototype.keys) {
  Object.prototype.keys = function() {
    var a = [];
    for (var key in this) {
      if (this.hasOwnProperty(key)) a.push(key);
    }
    return a;
  }
  Object.defineProperty(Object.prototype, "keys", {enumerable: false});
}

Object.prototype.equals = function(otherObject) {
  if (!otherObject) return false;
  var object = this, objectKeys = object.keys();
  if (!objectKeys.equals(otherObject.keys())) return false;
  return objectKeys.reduce(function(equal, key) {
    var value = object[key], otherValue = otherObject[key];
    var valueType = typeof value, otherValueType = typeof otherValue;
    if (valueType !== otherValueType) return false;
    // this will call Array.prototype.equals for arrays and Object.prototype.equals for objects
    return equal && (valueType === "object" ? value.equals(otherValue) : value === otherValue);
  }, true);
}
Object.defineProperty(Object.prototype, "equals", {enumerable: false});

此代码支持嵌套在对象中的数组和嵌套在数组中的对象。

您可以在下面的repl中看到一整套测试并自己测试代码:https://repl.it/Esfz/3

其他回答

与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

这里有很多好答案。我通常是这样做的-

if ( arr1.length === arr2.length && arr1.every((a1) => arr2.includes(a1)) ) {
   // logic
}

every()只在所有元素都通过给定的camparison时返回true思维方式如果在任何迭代中遇到错误,它将终止并返回false。时间复杂度为O(n*m)。

最短的

对于数字数组,请尝试:

a1==''+a2

变量a1=[1,2,3];变量a2=[1,2,3];console.log(a1==“”+a2)

注意:当数组还包含字符串时,此方法将不起作用,例如a2=[1,“2,3”]。

此脚本比较对象、数组和多维数组

function compare(a,b){
     var primitive=['string','number','boolean'];
     if(primitive.indexOf(typeof a)!==-1 && primitive.indexOf(typeof a)===primitive.indexOf(typeof b))return a===b;
     if(typeof a!==typeof b || a.length!==b.length)return false;
     for(i in a){
          if(!compare(a[i],b[i]))return false;
     }
     return true;
}

第一行检查它是否为原始类型。如果是,则比较这两个参数。

如果它们是对象。它遍历Object并递归检查每个元素。

用法:

var a=[1,2,[1,2]];
var b=[1,2,[1,2]];
var isEqual=compare(a,b);  //true

实用的方法

我认为将特定的实现称为“正确的方式”是错误的™Tomáš的解决方案是对基于字符串的数组比较的明显改进,但这并不意味着它客观上“正确”“。到底什么是正确的?它是最快的?它最灵活吗?它最容易理解吗?它是调试最快的吗?它使用最少的操作吗?它有任何副作用吗?没有一个解决方案可以拥有所有事情中最好的。

Tomáš’s可以说他的解决方案很快,但我也可以说这是不必要的复杂。它试图成为一个适用于所有阵列(无论是否嵌套)的一体化解决方案。事实上,它甚至不仅仅接受数组作为输入,还试图给出一个“有效”的答案。


泛型提供可重用性

我的回答将以不同的方式处理这个问题。我将从一个通用的arrayCompare过程开始,该过程只涉及遍历数组。然后,我们将构建其他基本的比较函数,如arrayEqual和arrayDeepEqual等

// 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和两个输入数组xs和ys。大多数情况下,我们所做的就是为输入数组中的每个元素调用f(x)(y)。如果用户定义的f返回false,我们将返回一个早期的false,这要归功于&&的短路评估。因此,是的,这意味着比较器可以提前停止迭代,并在不必要时防止循环通过输入数组的其余部分。


严格的比较

接下来,使用arrayCompare函数,我们可以轻松地创建其他可能需要的函数。我们将从基本数组Equal开始…

// 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

就这么简单。arrayEqual可以用arrayCompare和一个比较器函数来定义,该函数使用==(用于严格相等)来比较a和b。

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


松散的比较

我们可以使用==来定义arrayLooseEqual。现在,当比较1(数字)和“1”(字符串)时,结果将为真…

// 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,该比较器将检查a和b是否为数组。如果是,请重新应用arrayDeepCompare,否则将a和b与用户指定的比较器(f)进行比较。这允许我们将深度比较行为与实际比较单个元素的方式分开。也就是说,正如上面的例子所示,我们可以使用equal、looseEqual或我们制作的任何其他比较器进行深度比较。

因为arrayDeepCompare是currized的,所以我们也可以像前面的示例一样部分应用它

// 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比较器!我知道孩子们多么喜欢这些正则表达式…

它是最快的吗?不。但它可能也不需要。如果速度是衡量代码质量的唯一标准,那么很多真正优秀的代码就会被丢弃——这就是为什么我将这种方法称为实用方法。或者更公平地说,一种实用的方式。这个描述适合这个答案,因为我并不是说这个答案与其他答案相比只实用;这在客观上是正确的。我们用非常简单的代码实现了高度的实用性。没有其他代码可以说明我们没有获得此描述。

这是否是您的“正确”解决方案?这由你决定。没有人能为你做到这一点;只有你知道你的需求是什么。在几乎所有情况下,我都看重简单、实用和通用的代码,而不是聪明和快速的代码。你看重的可能不同,所以选择适合你的。


Edit

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