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

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


当前回答

根据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;
}

此函数接受默认为true的strict附加参数。这个严格的参数定义数组是否需要在内容和这些内容的顺序上完全相等,或者仅仅包含相同的内容。

例子:

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/

其他回答

我喜欢将Undercore库用于数组/对象密集型编码项目。。。在Undercore和Lodash中,无论您是比较数组还是对象,它看起来都是这样的:

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

Undercore is Equal文档Lodash isEqual文档

选择是否比较订单:

function arraysEqual(a1, a2, compareOrder) {
    if (a1.length !== a2.length) {
        return false;
    }

    return a1.every(function(value, index) {
        if (compareOrder) {
            return value === a2[index];
        } else {
            return a2.indexOf(value) > -1;
        }
    });
}

另一种代码很少的方法(使用Array reduce和Array includes):

arr1.length == arr2.length && arr1.reduce((a, b) => a && arr2.includes(b), true)

如果还要比较顺序的相等性:

arr1.length == arr2.length && arr1.reduce((a, b, i) => a && arr2[i], true)

长度检查确保一个数组中的元素集不仅仅是另一个数组的子集。缩减器用于遍历一个数组并搜索另一个数组中的每个项。如果找不到一项,reduce函数将返回false。在第一个示例中,正在测试是否包含元素第二个示例也检查订单

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

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

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

实用的方法

我认为将特定的实现称为“正确的方式”是错误的™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分解为小程序。这是一个有趣的练习,但并不是解决这个问题的最佳(最实用)方法。如果您感兴趣,可以查看此修订历史记录。