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

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


当前回答

当两个数组具有相同的元素但顺序不同时,代码将无法正确处理这种情况。

看看我的代码,看看你的示例,它比较了两个元素为数字的数组,你可以修改或扩展它以用于其他元素类型(通过使用.jjoin()而不是.toString())。

变量a1=[1,2,3];变量a2=[1,2,3];const arrayAreEqual=a1.sort().toString()==a2.sort(().toString();//如果两个数组具有相同的元素,则为true,否则为falseconsole.log(arrayAreEqual);

其他回答

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

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

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

请注意,您不再比较对象,而是比较对象的字符串表示形式。这可能不是你想要的。

此函数比较两个任意形状和尺寸的数组:

function equals(a1, a2) {

    if (!Array.isArray(a1) || !Array.isArray(a2)) {
        throw new Error("Arguments to function equals(a1, a2) must be arrays.");
    }

    if (a1.length !== a2.length) {
        return false;
    }

    for (var i=0; i<a1.length; i++) {
        if (Array.isArray(a1[i]) && Array.isArray(a2[i])) {
            if (equals(a1[i], a2[i])) {
                continue;
            } else {
                return false;
            }
        } else {
            if (a1[i] !== a2[i]) {
                return false;
            }
        }
    }

    return true;
}
let equals = (LHS, RHS) => {
    if (!(LHS instanceof Array)) return "false > L.H.S is't an array";
    if (!(RHS instanceof Array)) return "false > R.H.S is't an array";
    if (LHS.length != RHS.length) return false;
    let to_string = x => JSON.stringify(x.sort((a, b) => a - b));
    return to_string(LHS) == to_string(RHS);
  };

let l = console.log
l(equals([5,3,2],[3,2,5]))    // true
l(equals([3,2,5,3],[3,2,5]))  // false

我在回答这个问题https://stackoverflow.com/a/10316616/711085(此后已标记为该答案的副本)。在那里,您将发现一个DeepEquals实现,它处理许多情况,例如Map和Set以及数组和对象的任意嵌套。其中对==的非传递性和记录==vs==的讨论尤为重要。


对于OP的特殊问题,如果数组仅由数字、字符串和布尔值组成,而没有NaN,那么对于足够大的数组,最有效的方法是预编译函数:

function areSimpleArraysEqual(a,b) {
    // requires inputs be arrays of only Number, String, Boolean, and no NaN.
    // will propagate error if either array is undefined.
    if (a.length!=b.length)
        return false;
    for(let i=0; i<a.length; i++)
        if (a[i]!==b[i]) // using === equality
            return false;
    return true;
}

如果一个人的业务逻辑一直附加到数组的末尾,通过检查(a.length>0&&a[a.length-1]!==b[b.length-1])是否返回false;,在一些罕见的情况下,可以实现平均情况O(1)和最坏情况O(N)。

实用的方法

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