如何检查两个数组是否与 JavaScript 相等?

IT技术 javascript
2021-01-17 09:48:45
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);

alert(a == b + "|" + b == c);

演示

如何检查这些数组是否相等并获得一个方法,true如果它们相等则返回

jQuery 是否为此提供任何方法?

6个回答

这是你应该做的。请不要使用stringify< >

function arraysEqual(a, b) {
  if (a === b) return true;
  if (a == null || b == null) return false;
  if (a.length !== b.length) return false;

  // If you don't care about the order of the elements inside
  // the array, you should sort both arrays here.
  // Please note that calling sort on an array will modify that array.
  // you might want to clone your array first.

  for (var i = 0; i < a.length; ++i) {
    if (a[i] !== b[i]) return false;
  }
  return true;
}
如果数组包含一个对象,这将不起作用。
2021-03-20 09:48:45
@GijsjanB 如果数组包含相同的对象,它就可以工作。当然,如果对象不相同,并且您想比较对象本身,则它不起作用。但这不是问题。
2021-03-24 09:48:45
为什么要对代码进行排序?
2021-03-27 09:48:45
您能否在回答中解释为什么我们不应该使用stringifyor < >
2021-04-01 09:48:45
(这原本不是我的代码,但编写它的人不想发布它)
2021-04-05 09:48:45

[2021 更新日志:选项 4 的错误修正:js 对象没有总排序(甚至不包括NaN!=NaN'5'==5( '5'===5'2'<3等)),所以不能.sort(cmpFunc)在 Map.keys() 上使用(尽管你可以使用Object.keys(obj),因为即使是“数字”键也是字符串) .]

选项1

最简单的选项,几乎适用于所有情况,除了null!==undefined但它们都被转换为 JSON 表示null并被认为是相等的:

function arraysEqual(a1,a2) {
    /* WARNING: arrays must not contain {objects} or behavior may be undefined */
    return JSON.stringify(a1)==JSON.stringify(a2);
}

如果您的数组包含对象,这可能不起作用。这是否仍然适用于对象取决于 JSON 实现是否对键进行排序。例如, JSON{1:2,3:4}可能等于或不等于{3:4,1:2}; 这取决于实现,规范不做任何保证。[2017 年更新:实际上,ES6 规范现在保证对象键将按照 1) 整数属性,2) 属性按定义顺序迭代,然后 3) 符号属性按定义顺序迭代。因此,如果 JSON.stringify 实现遵循这一点,则相等的对象(在 === 意义上但在 == 意义上不是必需的)将字符串化为相等的值。需要更多的研究。所以我猜你可以用相反的顺序对一个具有属性的对象进行邪恶的克隆,但我无法想象它会偶然发生......]至少在 Chrome 上,JSON.stringify 函数倾向于按照定义的顺序返回键(至少我已经注意到),但这种行为在任何时候都可能发生变化,不应依赖。如果您选择不在列表中使用对象,这应该可以正常工作。如果您的列表中确实有对象都具有唯一 id,则可以执行a1.map(function(x)}{return {id:x.uniqueId}}). 如果您的列表中有任意对象,您可以继续阅读选项 #2。)

这也适用于嵌套数组。

然而,由于创建这些字符串和对其进行垃圾收集的开销,它的效率略低。


选项 2

历史版本 1 解决方案:


选项 3


选项 4:(2016 年编辑的延续)

这应该适用于大多数对象:

const STRICT_EQUALITY_BROKEN = (a,b)=> a===b;
const STRICT_EQUALITY_NO_NAN = (a,b)=> {
    if (typeof a=='number' && typeof b=='number' && ''+a=='NaN' && ''+b=='NaN')
        // isNaN does not do what you think; see +/-Infinity
        return true;
    else
        return a===b;
};
function deepEquals(a,b, areEqual=STRICT_EQUALITY_NO_NAN, setElementsAreEqual=STRICT_EQUALITY_NO_NAN) {
    /* compares objects hierarchically using the provided 
       notion of equality (defaulting to ===);
       supports Arrays, Objects, Maps, ArrayBuffers */
    if (a instanceof Array && b instanceof Array)
        return arraysEqual(a,b, areEqual);
    if (Object.getPrototypeOf(a)===Object.prototype && Object.getPrototypeOf(b)===Object.prototype)
        return objectsEqual(a,b, areEqual);
    if (a instanceof Map && b instanceof Map)
        return mapsEqual(a,b, areEqual);        
    if (a instanceof Set && b instanceof Set) {
        if (setElementsAreEqual===STRICT_EQUALITY_NO_NAN)
            return setsEqual(a,b);
        else
            throw "Error: set equality by hashing not implemented because cannot guarantee custom notion of equality is transitive without programmer intervention."
    }
    if ((a instanceof ArrayBuffer || ArrayBuffer.isView(a)) && (b instanceof ArrayBuffer || ArrayBuffer.isView(b)))
        return typedArraysEqual(a,b);
    return areEqual(a,b);  // see note[1] -- IMPORTANT
}

function arraysEqual(a,b, areEqual) {
    if (a.length!=b.length)
        return false;
    for(var i=0; i<a.length; i++)
        if (!deepEquals(a[i],b[i], areEqual))
            return false;
    return true;
}
function objectsEqual(a,b, areEqual) {
    var aKeys = Object.getOwnPropertyNames(a);
    var bKeys = Object.getOwnPropertyNames(b);
    if (aKeys.length!=bKeys.length)
        return false;
    aKeys.sort();
    bKeys.sort();
    for(var i=0; i<aKeys.length; i++)
        if (!areEqual(aKeys[i],bKeys[i])) // keys must be strings
            return false;
    return deepEquals(aKeys.map(k=>a[k]), aKeys.map(k=>b[k]), areEqual);
}
function mapsEqual(a,b, areEqual) { // assumes Map's keys use the '===' notion of equality, which is also the assumption of .has and .get methods in the spec; however, Map's values use our notion of the areEqual parameter
    if (a.size!=b.size)
        return false;
    return [...a.keys()].every(k=> 
        b.has(k) && deepEquals(a.get(k), b.get(k), areEqual)
    );
}
function setsEqual(a,b) {
    // see discussion in below rest of StackOverflow answer
    return a.size==b.size && [...a.keys()].every(k=> 
        b.has(k)
    );
}
function typedArraysEqual(a,b) {
    // we use the obvious notion of equality for binary data
    a = new Uint8Array(a);
    b = new Uint8Array(b);
    if (a.length != b.length)
        return false;
    for(var i=0; i<a.length; i++)
        if (a[i]!=b[i])
            return false;
    return true;
}
Demo (not extensively tested):

var nineTen = new Float32Array(2);
nineTen[0]=9; nineTen[1]=10;

> deepEquals(
    [[1,[2,3]], 4, {a:5,'111':6}, new Map([['c',7],['d',8]]), nineTen],
    [[1,[2,3]], 4, {111:6,a:5}, new Map([['d',8],['c',7]]), nineTen]
)
true

> deepEquals(
    [[1,[2,3]], 4, {a:'5','111':6}, new Map([['c',7],['d',8]]), nineTen],
    [[1,[2,3]], 4, {111:6,a:5}, new Map([['d',8],['c',7]]), nineTen],
    (a,b)=>a==b
)
true

请注意,如果使用==平等概念,那么要知道虚假值和强制意味着==平等不是可传递的例如''==0并且0=='0'但是''!='0'这与 Sets 相关:我认为人们无法以有意义的方式覆盖 Set 相等的概念。如果使用 Set 相等的内置概念(即===),则上述方法应该有效。但是,如果有人使用像 一样的非传递性概念==,你就会打开一堆蠕虫:即使你强迫用户在域上定义一个散列函数(hash(a)!=hash(b) 意味着 a!=b ) 我不确定这会有所帮助...当然可以做 O(N^2) 性能的事情并删除成对的==一个一个的项目,就像冒泡排序一样,然后再进行第二次 O(N^2) 传递以确认等价类中的事物实际上==是彼此之间的,并且也是!=如此配对的所有事物,但您仍然必须抛出一个运行时错误,如果你有一些强制进行......你也可能会得到奇怪的(但可能不是那么奇怪)与https://developer.mozilla.org/en-US/docs/Glossary/Falsy和Truthy 的边缘情况值(NaN==NaN 除外...但仅适用于集合!)。对于大多数同类数据类型集,这通常不是问题。

总结集合上递归等式的复杂性:

  • 集合相等是树同构问题http://logic.pdmi.ras.ru/~smal/files/smal_jass08_slides.pdf但稍微简单一点
  • 设置 A =? 集合 B 与B.has(k) for every k in A隐式使用===-equality ( [1,2,3]!==[1,2,3])同义,而不是递归相等 ( deepEquals([1,2,3],[1,2,3]) == true),所以两个new Set([[1,2,3]])不相等,因为我们不递归
  • 如果您使用的相等的递归概念不是 1) 自反性 (a=b 意味着 b=a) 和 2) 对称性 (a=a) 和 3) 传递性 (a=b b=c 意味着 a=c);这是等价类的定义
  • 相等 == 运算符显然不遵守这些属性中的许多
  • 甚至ecmascript 中的严格相等=== 运算符也不遵守这些属性,因为ecmascript严格相等比较算法有 NaN!=NaN; 这就是为什么当它们作为键出现时,许多本地数据类型喜欢SetMap“等同” NaN 以将它们视为相同的值
  • 只要我们强制并确保递归集合相等确实是可传递的、自反的和对称的,我们就可以确保不会发生任何可怕的错误。
    • 然后,我们可以通过递归地随机比较所有内容来进行 O(N^2) 次比较,这是非常低效的。没有神奇的算法可以让我们这样做,setKeys.sort((a,b)=> /*some comparison function*/)因为 ecmascript (''==0 and 0=='0', but ''!='0'...自己定义一个,这肯定是一个崇高的目标)。
    • 然而,我们可以确定.toStringJSON.stringify所有元素来帮助我们。然后我们将对它们进行排序,这为我们提供了潜在误报(两个不同的事物可能具有相同的字符串或 JSON 表示)的等价类(两个相同的事物不会具有相同的字符串 JSON 表示)。
      • 然而,这引入了它自己的性能问题,因为序列化相同的东西,然后一遍又一遍地序列化那个东西的子集,效率非常低。想象一棵嵌套Sets的树每个节点都属于 O(depth) 不同的序列化!
      • 即使这不是问题,如果所有序列化“提示”都相同,最坏情况的性能仍然是 O(N!)

因此,上面的实现声明如果项目只是简单的 ===(不是递归的 ===),则集合是相等的。这意味着它将为new Set([1,2,3])and返回 false new Set([1,2,3])如果您知道自己在做什么,只需稍加努力,就可以重写该部分代码。

(旁注:地图是 es6 词典。我不知道它们是否具有 O(1) 或 O(log(N)) 查找性能,但无论如何它们都是“有序”的,因为它们会跟踪顺序其中插入了键值对。但是,如果元素以不同的顺序插入其中,则两个 Map 是否应该相等的语义是不明确的。我在下面给出了一个 deepEquals 的示例实现,它认为两个映射甚至相等如果元素以不同的顺序插入其中。)

(注意 [1]:重要提示:平等概念:您可能想用自定义的平等概念覆盖注释行,您还必须在其他函数出现的任何地方更改该概念。例如,你还是不'不是你想要 NaN==NaN 吗?默认情况下不是这样。还有更奇怪的东西,比如 0=='0'。你是否认为两个对象是相同的,当且仅当它们在内存?请参阅https://stackoverflow.com/a/5447170/711085。您应该记录您使用的平等概念。)另请注意,其他天真地使用.toString并且.sort有时可能会下降的答案祈祷这样一个事实,0!=-0但被认为是平等的和对于几乎所有数据类型和 JSON 序列化,规范化为 0;是否-0==0 还应该记录在您的平等概念中,以及该表中的大多数其他内容(如 NaN 等)中。

您应该能够将上述内容扩展到 WeakMaps、WeakSets。不确定扩展到 DataViews 是否有意义。也应该能够扩展到 RegExps 等。

当你扩展它时,你意识到你做了很多不必要的比较。这是type我之前定义函数(解决方案#2)可以派上用场的地方;然后你可以立即发货。这是否值得(可能?不确定它在引擎盖下是如何工作的)表示类型的字符串的开销取决于您。然后您可以将调度程序(即 function )重写为deepEquals如下所示:

var dispatchTypeEquals = {
    number: function(a,b) {...a==b...},
    array: function(a,b) {...deepEquals(x,y)...},
    ...
}
function deepEquals(a,b) {
    var typeA = extractType(a);
    var typeB = extractType(a);
    return typeA==typeB && dispatchTypeEquals[typeA](a,b);
}
JSON.stringify(null) === 'null'(字符串“null”),而不是null.
2021-03-14 09:48:45
JSON.stringify 不保证将对象转换为相同的字符串。每一次。
2021-03-14 09:48:45
+1,一点评论:对于allTrue,您还可以array.every与返回数组元素值的函数一起使用
2021-03-21 09:48:45
oop,好像回到了那天我匆忙并欺骗自己误读了某人的评论的那一天,所以说了一个无关紧要的谎言(即 JSON.stringify(null)=='"null"')。我仍然没有看到与 OP 的问题有关的任何未定义或空值的问题,我在我的回答中没有提到。因此,我支持我的答案及其演示测试。
2021-03-21 09:48:45
undefined没有转换为字符串'null'......你能花点时间了解一下 JSON.stringify 是如何工作的吗?
2021-04-02 09:48:45

jQuery 没有比较数组的方法。然而,Underscore(或类似的 Lodash 库)确实有这样一个方法:isEqual,它也可以处理各种其他情况(如对象文字)。坚持提供的例子:

var a=[1,2,3];
var b=[3,2,1];
var c=new Array(1,2,3);

alert(_.isEqual(a, b) + "|" + _.isEqual(b, c));

顺便说一句:Underscore 有很多其他 jQuery 也没有的方法,所以它是对 jQuery 的一个很好的补充。

编辑:正如评论中指出的那样,上面的内容现在仅在两个数组的元素顺序相同时才有效,即:

_.isEqual([1,2,3], [1,2,3]); // true
_.isEqual([1,2,3], [3,2,1]); // false

幸运的是 Javascript 有一个内置的方法来解决这个确切的问题,sort

_.isEqual([1,2,3].sort(), [3,2,1].sort()); // true
Underscore已被更高级的库lodash 取代由于它是下划线的超集,它也支持 _.isEqual(a, b)。检查了解更多详情。
2021-03-13 09:48:45
Underscorejs 是我日常使用的库。它的简单性肯定应该更高。
2021-03-14 09:48:45
我喜欢 lodash,它确实包含了 Underscore 的超级功能集……但它并不“优越”,也没有“取代”Underscore。一方面,Lodash 已经过元编程和微优化,其源代码基本上不可读。当(例如)您不小心将错误的参数传递给 Lodash/Underscore 函数并且必须调试正在发生的事情时,这很重要。在这种情况下,Underscore非常优越,因为(与 Lodash 不同)您实际上可以阅读源代码。最终,两个库都不优于另一个库,它们只是具有不同的优势和劣势。
2021-03-25 09:48:45
回到手头的话题,这提醒了false|false_.isEqual(a,b)根据它们的顺序比较数组的元素,因此如果需要对顺序不敏感的比较,则必须在比较之前对数组进行排序。
2021-04-03 09:48:45
作为旁注,这两个库的作者(John-David Dalton 和 Jeremy Ashkenas)最近讨论了合并它们的前景,因为它们有共同之处。但是(如果您阅读 GitHub 主题:github.com/jashkenas/underscore/issues/2182,您会发现)这不是一个显而易见的决定。事实上,AFAIK 在此后的 3 个多月内一直没有做出任何决定,正是因为 Underscore 具有 Underscore 用户不想丢失的可读源代码等优势。
2021-04-08 09:48:45

对于像数字和字符串这样的原始值,这是一个简单的解决方案:

a = [1,2,3]

b = [3,2,1]

a.sort().toString() == b.sort().toString() 

调用sort()will 确保元素的顺序无关紧要。toString()调用将创建一个字符串,其中的值以逗号分隔,因此可以测试两个字符串的相等性。

@feihcsim 你错了。[12, 34, 56].toString() //results: "12,34,56" while [1, 23, 456].toString() // results: "1,23,456" 因此,它们不相等
2021-03-10 09:48:45
a.sort()不仅返回排序版本。它改变了数组本身,这可能会以意想不到的方式影响一个人的应用程序。
2021-03-16 09:48:45
啊,你是对的 - 但问题仍然存在:['1,2',3].toString() === [1,'2,3'].toString()是误报
2021-03-27 09:48:45
我不明白为什么这会被否决:这是迄今为止唯一真正使(未指定)示例正确的解决方案......
2021-04-05 09:48:45
如果您的数组包含简单值以外的任何内容,请小心。Array.prototype.sort()是浅的,Array.prototype.toString()并将对象转换为[object Object]任何嵌入的数组并将其展平,这可能会导致误报。
2021-04-07 09:48:45

使用 JavaScript 1.6 版就这么简单:

Array.prototype.equals = function( array ) {
  return this.length == array.length && 
         this.every( function(this_i,i) { return this_i == array[i] } )  
  }

例如,[].equals([])给出true,而[1,2,3].equals( [1,3,2] )产生false

行。我刚刚讨论了这个话题,因为我正在寻找相同的东西,但没有索引相等。但好吧,你是对的。
2021-03-15 09:48:45
@ChetPrickles 取决于你想要什么,像往常一样:-) 你想undefined等于null0 等 - 还是不。
2021-03-19 09:48:45
一般建议不要修改/扩展现有的全局对象。
2021-04-03 09:48:45
最好使用 === 而不是 == 对吗?
2021-04-06 09:48:45
如果您有 2 个这样的数组[3,3,3] and [1,2,3],它就不起作用: ,因为您的代码将返回 true 而不是。所以你必须every在两边都做
2021-04-06 09:48:45