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

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


当前回答

这是一个字体版本:

//https://stackoverflow.com/a/16436975/2589276
export function arraysEqual<T>(a: Array<T>, b: Array<T>): boolean {
    if (a === b) return true
    if (a == null || b == null) return false
    if (a.length != b.length) return false

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

//https://stackoverflow.com/a/16436975/2589276
export function arraysDeepEqual<T>(a: Array<T>, b: Array<T>): boolean {
    return JSON.stringify(a) === JSON.stringify(b)
}

摩卡的一些测试用例:

it('arraysEqual', function () {
    let a = [1,2]
    let b = [1,2]
    let c = [2,3]
    let d = [2, 3]
    let e = ['car','apple','banana']
    let f = ['car','apple','banana']
    let g = ['car','apple','banan8']

    expect(arraysEqual(a, b)).to.equal(true)
    expect(arraysEqual(c, d)).to.equal(true)
    expect(arraysEqual(a, d)).to.equal(false)
    expect(arraysEqual(e, f)).to.equal(true)
    expect(arraysEqual(f, g)).to.equal(false)
})

it('arraysDeepEqual', function () {
    let a = [1,2]
    let b = [1,2]
    let c = [2,3]
    let d = [2, 3]
    let e = ['car','apple','banana']
    let f = ['car','apple','banana']
    let g = ['car','apple','banan8']
    let h = [[1,2],'apple','banan8']
    let i = [[1,2],'apple','banan8']
    let j = [[1,3],'apple','banan8']

    expect(arraysDeepEqual(a, b)).to.equal(true)
    expect(arraysDeepEqual(c, d)).to.equal(true)
    expect(arraysDeepEqual(a, d)).to.equal(false)
    expect(arraysDeepEqual(e, f)).to.equal(true)
    expect(arraysDeepEqual(f, g)).to.equal(false)
    expect(arraysDeepEqual(h, i)).to.equal(true)
    expect(arraysDeepEqual(h, j)).to.equal(false)
})

其他回答

与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

实用的方法

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

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

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

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

我知道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。

=)

以下是我的解决方案:

/**
 * Tests two data structures for equality
 * @param {object} x
 * @param {object} y
 * @returns {boolean}
 */
var equal = function(x, y) {
    if (typeof x !== typeof y) return false;
    if (x instanceof Array && y instanceof Array && x.length !== y.length) return false;
    if (typeof x === 'object') {
        for (var p in x) if (x.hasOwnProperty(p)) {
            if (typeof x[p] === 'function' && typeof y[p] === 'function') continue;
            if (x[p] instanceof Array && y[p] instanceof Array && x[p].length !== y[p].length) return false;
            if (typeof x[p] !== typeof y[p]) return false;
            if (typeof x[p] === 'object' && typeof y[p] === 'object') { if (!equal(x[p], y[p])) return false; } else
            if (x[p] !== y[p]) return false;
        }
    } else return x === y;
    return true;
};

适用于任何嵌套的数据结构,显然忽略了对象的方法。甚至不要考虑用这个方法扩展Object.prototype,当我尝试过一次时,jQuery坏了;)

对于大多数阵列,它仍然比大多数串行化解决方案更快。这可能是对象记录数组最快的比较方法。