如何检查两个数组是否与JavaScript相等?
var a = [1, 2, 3]; var b = [3, 2, 1]; var c = new Array(1, 2, 3); alert(a == b + "|" + b == c);
演示
我如何检查这些数组是否相等,并得到一个方法,如果它们相等,则返回true
。
jQuery提供任何方法吗?
这是你应该做的。 请不要使用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. for (var i = 0; i < a.length; ++i) { if (a[i] !== b[i]) return false; } return true; }
选项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实现是否对键进行sorting,例如, {1:2,3:4}
的JSON可能不等于{3:4,1:2}
;这取决于实现,规范不能保证任何至less在Chrome上,JSON.stringify函数倾向于按照定义的顺序返回键(至less我注意到了),但是这个行为在任何时候都是非常容易改变的,不应该被依赖,如果你select不使用列表中的对象,这应该可以正常工作,如果你的列表中有对象, id,你可以做a1.map(function(x)}{return {id:x.uniqueId}})
如果你的列表中有任意的对象,你可以阅读选项#2。
这也适用于嵌套数组。
但是,由于创build这些string和垃圾收集的开销,它的效率稍低。
选项2
更“适当的”选项,你可以重写处理特殊情况(如常规对象和null / undefined和自定义对象,如果你愿意):
// generally useful functions function type(x) { // does not work in general, but works on JSONable objects we care about... modify as you see fit // eg type(/asdf/g) --> "[object RegExp]" return Object.prototype.toString.call(x); } function zip(arrays) { // eg zip([[1,2,3],[4,5,6]]) --> [[1,4],[2,5],[3,6]] return arrays[0].map(function(_,i){ return arrays.map(function(array){return array[i]}) }); }
// helper functions function allCompareEqual(array) { // eg allCompareEqual([2,2,2,2]) --> true // does not work with nested arrays or objects return array.every(function(x){return x==array[0]}); } function isArray(x){ return type(x)==type([]) } function getLength(x){ return x.length } function allTrue(array){ return array.reduce(function(a,b){return a&&b},true) } // eg allTrue([true,true,true,true]) --> true // or just array.every(function(x){return x});
function allDeepEqual(things) { // works with nested arrays if( things.every(isArray) ) return allCompareEqual(things.map(getLength)) // all arrays of same length && allTrue(zip(things).map(allDeepEqual)); // elements recursively equal //else if( this.every(isObject) ) // return {all have exactly same keys, and for // each key k, allDeepEqual([o1[k],o2[k],...])} // eg ... && allTrue(objectZip(objects).map(allDeepEqual)) //else if( ... ) // extend some more else return allCompareEqual(things); }
演示:
allDeepEqual([ [], [], [] ]) true allDeepEqual([ [1], [1], [1] ]) true allDeepEqual([ [1,2], [1,2] ]) true allDeepEqual([ [[1,2],[3]], [[1,2],[3]] ]) true allDeepEqual([ [1,2,3], [1,2,3,4] ]) false allDeepEqual([ [[1,2],[3]], [[1,2],[],3] ]) false allDeepEqual([ [[1,2],[3]], [[1],[2,3]] ]) false allDeepEqual([ [[1,2],3], [1,[2,3]] ]) false
要像常规function那样使用它,请执行以下操作:
function allDeepEqual2() { return allDeepEqual([].slice.call(arguments)); }
演示:
allDeepEqual2([[1,2],3], [[1,2],3]) true
选项3
编辑 :这是2016年,我以前的过于复杂的答案是窃听我。 这个recursion的,必要的“recursion编程101”实现保持代码非常简单,并且在最早的可能点(给我们效率)失败。 它也不会产生多余的短暂的数据结构(不是一般的函数式编程有什么问题,而只是在这里保持清洁)。
如果我们想把它应用到一个非空的数组数组中,我们可以使用serialOfArrays.reduce(arraysEqual)。
这是它自己的函数,而不是使用Object.defineProperties附加到Array.prototype,因为如果我们传递一个未定义的值,那么这将失败并带有关键错误(但是,如果你想这样做,这是一个很好的devise决定) 。
这只能回答OP的原始问题。
function arraysEqual(a,b) { /* Array-aware equality checker: Returns whether arguments a and b are == to each other; however if they are equal-lengthed arrays, returns whether their elements are pairwise == to each other recursively under this definition. */ if (a instanceof Array && b instanceof Array) { if (a.length!=b.length) // assert same length return false; for(var i=0; i<a.length; i++) // assert each element equal if (!arraysEqual(a[i],b[i])) return false; return true; } else { return a==b; // if not both arrays, should be the same } }
例子:
arraysEqual([[1,2],3], [[1,2],3]) true arraysEqual([1,2,3], [1,2,3,4]) false arraysEqual([[1,2],[3]], [[1,2],[],3]) false arraysEqual([[1,2],[3]], [[1],[2,3]]) false arraysEqual([[1,2],3], undefined) false arraysEqual(undefined, undefined) true arraysEqual(1, 2) false arraysEqual(null, null) true arraysEqual(1, 1) true arraysEqual([], 1) false arraysEqual([], undefined) false arraysEqual([], []) true
如果你想用js Objects将它应用到类似JSON的数据结构,你可以这样做。 幸运的是,我们保证所有的对象键都是唯一的,所以遍历对象OwnProperties并按键sorting,然后声明sorting后的键列是相等的,值列是相等的,只是recursion。 我们可以扩展到包括地图(这里的关键字也是唯一的)。 (但是,如果我们把它扩展到集合,我们遇到了树同构问题~smal/files/smal_jass08_slides.pdf – 幸运的是它不像一般图同构一样困难;有实际上是一个O(#vertices)algorithm来解决这个问题,但是如果你有一个由许多看起来不可区分的对象组成的集合,但是进一步检查其中的一些对象,病态就会变得非常复杂可能会因为深入研究而有所不同,也可以通过使用散列法来排除几乎所有的情况。)
scheme4 :(续编2016)
这应该适用于大多数对象:
function deepEquals(a,b) { if (a instanceof Array && b instanceof Array) return arraysEqual(a,b); if (Object.getPrototypeOf(a)===Object.prototype && Object.getPrototypeOf(b)===Object.prototype) return objectsEqual(a,b); if (a instanceof Map && b instanceof Map) return mapsEqual(a,b); if (a instanceof Set && b instanceof Set) throw "Error: set equality by hashing not implemented." if ((a instanceof ArrayBuffer || ArrayBuffer.isView(a)) && (b instanceof ArrayBuffer || ArrayBuffer.isView(b))) return typedArraysEqual(a,b); return a==b; // see note[1] } function arraysEqual(a,b) { if (a.length!=b.length) return false; for(var i=0; i<a.length; i++) if (!deepEquals(a[i],b[i])) return false; return true; } function objectsEqual(a,b) { 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 (aKeys[i]!=bKeys[i]) // keys must be strings return false; return deepEquals(aKeys.map(k=>a[k]), aKeys.map(k=>b[k])); } function mapsEqual(a,b) { if (a.size!=b.size) return false; var aPairs = Array.from(a); var bPairs = Array.from(b); aPairs.sort((x,y) => x[0]<y[0]); bPairs.sort((x,y) => x[0]<y[0]); for(var i=0; i<a.length; i++) if (!deepEquals(aPairs[i][0],bPairs[i][0]) || !deepEquals(aPairs[i][1],bPairs[i][1])) return false; return true; } function typedArraysEqual(a,b) { 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; }
演示(未广泛testing):
var nineTen = new Float32Array(2); nineTen[0]=9; nineTen[1]=10; deepEquals( [[1,[2,3]], 4, {a:5,b:6}, new Map([['c',7],['d',8]]), nineTen], [[1,[2,3]], 4, {b:6,a:5}, new Map([['d',8],['c',7]]), nineTen] )
(注:Maps是es6字典,我不知道它们是否具有O(1)或O(log(N))查询性能,但无论如何,它们是“有序”的,因为它们跟踪在这种情况下,如果元素以不同的顺序插入到两个元素中,那么两个元素是否相等的语义是模棱两可的,下面我们给出一个deepEquals的例子,即使元素以不同的顺序插入到它们中。)
(注意[1]:你可能想用一个自定义的平等概念覆盖上面提到的那一行,你也可以在其他任何地方改变它的function,例如,你或者你不想让NaN = = NaN?默认情况下不是这样,还有更奇怪的东西,例如0 =='0',请参阅https://stackoverflow.com/a/5447170/711085 )
你应该能够把上面的扩展到WeakMaps,WeakSets。 不知道扩展到DataView是否有意义。 也应该能够扩展到RegExps大概,等等。
随着你的扩展,你意识到你做了很多不必要的比较。 这是我之前定义的type
函数(解决scheme#2)可以派上用场的地方; 那么你可以立即派遣。 是否值得开销(可能?不知道它是如何工作的)引擎代表的types取决于你。 您可以重写调度器,即函数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); }
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));
顺便说一句:下划线还有很多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
使用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
。
对于数字和string这样的原始值,这是一个简单的解决scheme
a = [1,2,3] b = [3,2,1] a.sort().toString() == b.sort().toString()
对sort()
的调用将确保元素的顺序无关紧要。 toString()
调用将创build一个逗号分隔的string,以便可以testing两个string是否相等。
即使这看起来超级简单,有时候也是非常有用的。 如果您只需要查看两个数组是否具有相同的项目,并且顺序相同,请尝试以下操作:
[1, 2, 3].toString() == [1, 2, 3].toString() true [1, 2, 3,].toString() == [1, 2, 3].toString() true [1,2,3].toString() == [1, 2, 3].toString() true
但是,这不适用于高级模式,例如:
[[1,2],[3]].toString() == [[1],[2,3]].toString() true
这取决于你需要什么。
jQuery有这种深度recursion比较的方法。
一个本土的通用严格平等检查可能如下所示:
function deepEquals(obj1, obj2, parents1, parents2) { "use strict"; var i; // compare null and undefined if (obj1 === undefined || obj2 === undefined || obj1 === null || obj2 === null) { return obj1 === obj2; } // compare primitives if (typeof (obj1) !== 'object' || typeof (obj2) !== 'object') { return obj1.valueOf() === obj2.valueOf(); } // if objects are of different types or lengths they can't be equal if (obj1.constructor !== obj2.constructor || (obj1.length !== undefined && obj1.length !== obj2.length)) { return false; } // iterate the objects for (i in obj1) { // build the parents list for object on the left (obj1) if (parents1 === undefined) parents1 = []; if (obj1.constructor === Object) parents1.push(obj1); // build the parents list for object on the right (obj2) if (parents2 === undefined) parents2 = []; if (obj2.constructor === Object) parents2.push(obj2); // walk through object properties if (obj1.propertyIsEnumerable(i)) { if (obj2.propertyIsEnumerable(i)) { // if object at i was met while going down here // it's a self reference if ((obj1[i].constructor === Object && parents1.indexOf(obj1[i]) >= 0) || (obj2[i].constructor === Object && parents2.indexOf(obj2[i]) >= 0)) { if (obj1[i] !== obj2[i]) { return false; } continue; } // it's not a self reference so we are here if (!deepEquals(obj1[i], obj2[i], parents1, parents2)) { return false; } } else { // obj2[i] does not exist return false; } } } return true; };
testing:
// message is displayed on failure // clean console === all tests passed function assertTrue(cond, msg) { if (!cond) { console.log(msg); } } var a = 'sdf', b = 'sdf'; assertTrue(deepEquals(b, a), 'Strings are equal.'); b = 'dfs'; assertTrue(!deepEquals(b, a), 'Strings are not equal.'); a = 9; b = 9; assertTrue(deepEquals(b, a), 'Numbers are equal.'); b = 3; assertTrue(!deepEquals(b, a), 'Numbers are not equal.'); a = false; b = false; assertTrue(deepEquals(b, a), 'Booleans are equal.'); b = true; assertTrue(!deepEquals(b, a), 'Booleans are not equal.'); a = null; assertTrue(!deepEquals(b, a), 'Boolean is not equal to null.'); a = function () { return true; }; assertTrue(deepEquals( [ [1, 1, 1], [2, 'asdf', [1, a]], [3, { 'a': 1.0 }, true] ], [ [1, 1, 1], [2, 'asdf', [1, a]], [3, { 'a': 1.0 }, true] ]), 'Arrays are equal.'); assertTrue(!deepEquals( [ [1, 1, 1], [2, 'asdf', [1, a]], [3, { 'a': 1.0 }, true] ], [ [1, 1, 1], [2, 'asdf', [1, a]], [3, { 'a': '1' }, true] ]), 'Arrays are not equal.'); a = { prop: 'val' }; a.self = a; b = { prop: 'val' }; b.self = a; assertTrue(deepEquals(b, a), 'Immediate self referencing objects are equal.'); a.prop = 'shmal'; assertTrue(!deepEquals(b, a), 'Immediate self referencing objects are not equal.'); a = { prop: 'val', inside: {} }; a.inside.self = a; b = { prop: 'val', inside: {} }; b.inside.self = a; assertTrue(deepEquals(b, a), 'Deep self referencing objects are equal.'); b.inside.self = b; assertTrue(!deepEquals(b, a), 'Deep self referencing objects are not equeal. Not the same instance.'); b.inside.self = {foo: 'bar'}; assertTrue(!deepEquals(b, a), 'Deep self referencing objects are not equal. Completely different object.'); a = {}; b = {}; a.self = a; b.self = {}; assertTrue(!deepEquals(b, a), 'Empty object and self reference of an empty object.');
基于Tim James的回答和Fox32的评论,下面应该检查空值,假设两个空值不相等。
function arrays_equal(a,b) { return !!a && !!b && !(a<b || b<a); } > arrays_equal([1,2,3], [1,3,4]) false > arrays_equal([1,2,3], [1,2,3]) true > arrays_equal([1,3,4], [1,2,3]) false > arrays_equal(null, [1,2,3]) false > arrays_equal(null, null) false
使用map()
和reduce()
:
function arraysEqual (a1, a2) { return a1 === a2 || ( a1 !== null && a2 !== null && a1.length === a2.length && a1 .map(function (val, idx) { return val === a2[idx]; }) .reduce(function (prev, cur) { return prev && cur; }, true) ); }
有没有简单的方法来做到这一点。 我也需要这个,但想要一个函数,可以采取任何两个variables和testing平等。 这包括非对象值,对象,数组和任何级别的嵌套。
在你的问题中,你提到想忽略数组中值的顺序。 我的解决scheme本身并不这样做,但你可以通过排列数组之前比较平等来实现它
我也想要将非对象转换为string,以便[1,2] === [“1”,2]
由于我的项目使用了UnderscoreJs,所以我决定把它变成一个混合而不是一个独立的函数。
你可以在http://jsfiddle.net/nemesarial/T44W4/上testing
这是我的mxin:
_.mixin({ /** Tests for the equality of two variables valA: first variable valB: second variable stringifyStatics: cast non-objects to string so that "1"===1 **/ equal:function(valA,valB,stringifyStatics){ stringifyStatics=!!stringifyStatics; //check for same type if(typeof(valA)!==typeof(valB)){ if((_.isObject(valA) || _.isObject(valB))){ return false; } } //test non-objects for equality if(!_.isObject(valA)){ if(stringifyStatics){ var valAs=''+valA; var valBs=''+valB; ret=(''+valA)===(''+valB); }else{ ret=valA===valB; } return ret; } //test for length if(_.size(valA)!=_.size(valB)){ return false; } //test for arrays first var isArr=_.isArray(valA); //test whether both are array or both object if(isArr!==_.isArray(valB)){ return false; } var ret=true; if(isArr){ //do test for arrays _.each(valA,function(val,idx,lst){ if(!ret){return;} ret=ret && _.equal(val,valB[idx],stringifyStatics); }); }else{ //do test for objects _.each(valA,function(val,idx,lst){ if(!ret){return;} //test for object member exists if(!_.has(valB,idx)){ ret=false; return; } // test for member equality ret=ret && _.equal(val,valB[idx],stringifyStatics); }); } return ret; } });
这是你如何使用它:
_.equal([1,2,3],[1,2,"3"],true)
为了演示嵌套,你可以这样做:
_.equal( ['a',{b:'b',c:[{'someId':1},2]},[1,2,3]], ['a',{b:'b',c:[{'someId':"1"},2]},["1",'2',3]] ,true);
如果你想检查对象的数组是否相等 ,顺序不重要,即
areEqual([{id: "0"}, {id: "1"}], [{id: "1"}, {id: "0"}]) // true
你会想先sorting数组。 lodash拥有你需要的所有工具,通过结合sortBy
和isEqual
:
// arr1 & arr2: Arrays of objects // sortProperty: the property of the object with which you want to sort // Note: ensure every object in both arrays has your chosen sortProperty // For example, arr1 = [{id: "v-test_id0"}, {id: "v-test_id1"}] // and arr2 = [{id: "v-test_id1"}, {id: "v-test_id0"}] // sortProperty should be 'id' function areEqual (arr1, arr2, sortProperty) { return _.areEqual(_.sortBy(arr1, sortProperty), _.sortBy(arr2, sortProperty)) }
编辑:由于sortBy
返回一个新的数组,没有必要在sorting之前克隆你的数组。 原始数组不会被突变。
请注意,对于lodash的isEqual
, 订单确实很重要 。 如果sortBy
首先不应用于每个数组,则上面的示例将返回false
。
如果你正在使用lodash而不想修改任何数组,你可以使用函数_.xor() 。 它将两个数组作为集合进行比较,并返回包含其差异的集合。 如果这个差值的长度是零,那么这两个数组本质上是相等的:
var a = [1, 2, 3]; var b = [3, 2, 1]; var c = new Array(1, 2, 3); _.xor(a, b).length === 0 true _.xor(b, c).length === 0 true
检查数组大小后,通过for循环检查每个值。
function equalArray(a, b) { if (a.length === b.length) { for (var i = 0; i < a.length; i++) { if (a[i] !== b[i]) { return false; } } return true; } else { return false; } }
它处理所有可能的东西,甚至参照对象的结构。 你可以在代码结尾看到这个例子。
var deepCompare = (function() { function internalDeepCompare (obj1, obj2, objects) { var i, objPair; if (obj1 === obj2) { return true; } i = objects.length; while (i--) { objPair = objects[i]; if ( (objPair.obj1 === obj1 && objPair.obj2 === obj2) || (objPair.obj1 === obj2 && objPair.obj2 === obj1) ) { return true; } } objects.push({obj1: obj1, obj2: obj2}); if (obj1 instanceof Array) { if (!(obj2 instanceof Array)) { return false; } i = obj1.length; if (i !== obj2.length) { return false; } while (i--) { if (!internalDeepCompare(obj1[i], obj2[i], objects)) { return false; } } } else { switch (typeof obj1) { case "object": // deal with null if (!(obj2 && obj1.constructor === obj2.constructor)) { return false; } if (obj1 instanceof RegExp) { if (!(obj2 instanceof RegExp && obj1.source === obj2.source)) { return false; } } else if (obj1 instanceof Date) { if (!(obj2 instanceof Date && obj1.getTime() === obj2.getTime())) { return false; } } else { for (i in obj1) { if (obj1.hasOwnProperty(i)) { if (!(obj2.hasOwnProperty(i) && internalDeepCompare(obj1[i], obj2[i], objects))) { return false; } } } } break; case "function": if (!(typeof obj2 === "function" && obj1+"" === obj2+"")) { return false; } break; default: //deal with NaN if (obj1 !== obj2 && obj1 === obj1 && obj2 === obj2) { return false; } } } return true; } return function (obj1, obj2) { return internalDeepCompare(obj1, obj2, []); }; }()); /* var a = [a, undefined, new Date(10), /.+/, {a:2}, function(){}, Infinity, -Infinity, NaN, 0, -0, 1, [4,5], "1", "-1", "a", null], b = [b, undefined, new Date(10), /.+/, {a:2}, function(){}, Infinity, -Infinity, NaN, 0, -0, 1, [4,5], "1", "-1", "a", null]; deepCompare(a, b); */
这个方法很糟糕,但我已经把它留在这里作为参考,所以其他人避免这个path:
从@ninjagecko使用选项1最适合我:
Array.prototype.equals = function(array) { return array instanceof Array && JSON.stringify(this) === JSON.stringify(array) ; } a = [1, [2, 3]] a.equals([[1, 2], 3]) // false a.equals([1, [2, 3]]) // true
它也将处理空和未定义的情况,因为我们将这添加到数组的原型,并检查另一个参数也是一个数组。
var a= [1, 2, 3, '3']; var b = [1, 2, 3]; var c = a.filter(function (i) { return ! ~b.indexOf(i); }); alert(c.length);