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

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


当前回答

递归在NESTED数组上工作(&W):

function ArrEQ(a1,a2){
   return( 
        //:Are both elements arrays?
        Array.isArray(a1)&&Array.isArray(a2) 
        ?
        //:Yes: Test each entry for equality:
        a1.every((v,i)=>(ArrEQ(v,a2[i])))
        :
        //:No: Simple Comparison:
        (a1===a2)
   );;
};;

console.log( "Works With Nested Arrays:" );
console.log( ArrEQ( 
    [1,2,3,[4,5,[6,"SAME/IDENTICAL"]]],
    [1,2,3,[4,5,[6,"SAME/IDENTICAL"]]]
));;     
console.log( ArrEQ( 
    [1,2,3,[4,5,[6,"DIFFERENT:APPLES" ]]],
    [1,2,3,[4,5,[6,"DIFFERENT:ORANGES"]]]
));;  

其他回答

我知道JSON.stringfy在处理大型数据集时速度很慢,但如果使用模板文字呢?

例子:

const a = [1, 2, 3];
const b = [1, 2, 'test'];

const a_string = Array.isArray(a) && `${a}`;
const b_string = Array.isArray(b) && `${b}`;

const result = (a === b);

console.log(result);

当然,考虑到您正在使用ES6。

=)

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

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

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

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

我的解决方案比较对象,而不是数组。这将以与Tomáš相同的方式工作,因为数组是对象,但没有警告:

Object.prototype.compare_to = function(comparable){
    
    // Is the value being compared an object
    if(comparable instanceof Object){
        
        // Count the amount of properties in @comparable
        var count_of_comparable = 0;
        for(p in comparable) count_of_comparable++;
        
        // Loop through all the properties in @this
        for(property in this){
            
            // Decrements once for every property in @this
            count_of_comparable--;
            
            // Prevents an infinite loop
            if(property != "compare_to"){
                
                // Is the property in @comparable
                if(property in comparable){
                    
                    // Is the property also an Object
                    if(this[property] instanceof Object){
                        
                        // Compare the properties if yes
                        if(!(this[property].compare_to(comparable[property]))){
                            
                            // Return false if the Object properties don't match
                            return false;
                        }
                    // Are the values unequal
                    } else if(this[property] !== comparable[property]){
                        
                        // Return false if they are unequal
                        return false;
                    }
                } else {
                
                    // Return false if the property is not in the object being compared
                    return false;
                }
            }
        }
    } else {
        
        // Return false if the value is anything other than an object
        return false;
    }
    
    // Return true if their are as many properties in the comparable object as @this
    return count_of_comparable == 0;
}

尽管这有很多答案,但我相信这会有所帮助:

const newArray = [ ...new Set( [...arr1, ...arr2] ) ]

这个问题中没有说明数组的结构是什么样子的,所以如果你确定你的数组中既没有嵌套的数组也没有对象(这发生在我身上,这就是我得到这个答案的原因),上面的代码就会起作用。

发生的情况是,我们使用扩展运算符(…)将两个数组合并,然后使用Set消除任何重复。一旦你有了这些,你就可以比较它们的大小,如果三个数组都有相同的大小,那么你就可以去做了。

这个答案也忽略了元素的顺序,正如我所说的,确切的情况发生在我身上,所以也许有人在同样的情况下会在这里结束(正如我所做的那样)。


编辑1。

回答德米特里·格林科的问题:“你为什么在这里使用扩展运算符(…)-……新设置?它不起作用”

考虑以下代码:

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' ]

这就是区别,前者会给我一个集合,它也会工作,因为我可以得到集合的大小,但后者给了我所需的数组,更直接的是分辨率。

实用的方法

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