# Array Comparison 1

## JavaScript performance comparison

Revision 18 of this test case created by C5H8NNaO4

## Preparation code

<script>
Benchmark.prototype.setup = function() {
var a = [];
var b = [];
var obj;
var arr;
var max = 10000;

for (var i = 0; i < max; i++) {
if (i % 5) {
arr = [1, 2];
b[max - i - 1] = arr;
a[i] = arr;
} else if (i % 2) {
obj = {
"hello": "world"
};
b[max - i - 1] = obj;
a[i] = obj;
} else {
b[max - i - 1] = i % 100;
a[i] = i % 100;
}
}

function sameElementsBrunoLM(a, b) {
// immediately discard if they are of different sizes
if (a.length != b.length) return false;

b = b.slice(0); // clone to keep original values after the function
a.forEach(function(e) {
var i;
if ((i = b.indexOf(e)) != -1) b.splice(i, 1);
});

return !b.length;
}

function sameElementsBruno(a, b) {

// if length is not the same then must not be equal
if (a.length != b.length) return false;

// do an initial sort which will group types
a.sort();
b.sort();

for (var i = 0; i < a.length; i++) {

// if primitive then comparison will do
if (isPrimitive(a[i]) && isPrimitive(b[i])) {

if (a[i] != b[i]) return false;
}
// if not primitive then we will need to check for obj ref
else if (!isPrimitive(a[i]) && !isPrimitive(b[i])) {
if (!a[i].__aCount) a[i].__aCount = 0;
if (!b[i].__bCount) b[i].__bCount = 0;
a[i].__aCount = a[i].__aCount + 1;
b[i].__bCount = b[i].__bCount + 1;
}
// if both types are not the same then this array
// contains different number of primitives
else {
return false;
}

}
for (var i = 0; i < a.length; i++) {
if (!isPrimitive(a[i])) {
if (!a[i].__aCount) {
return false;
}
if (!a[i].__bCount) {
return false;
}
if (a[i].__aCount != a[i].__bCount) {
return false;
}
}
}
// if it gets this far it must be the same
return true;
}

function isPrimitive(arg) {
var type = typeof arg;
return arg == null || (type != "object" && type != "function");
}

function sameElementsHamidi(a, b) {
if (a.length != b.length) {
return false;
}
var ourB = b.concat();
return a.every(function(item) {
var index = ourB.indexOf(item);
if (index < 0) {
return false;
} else {
ourB.splice(index, 1);
return true;
}
});
}

function sameElementsBergi(a) {
var map, maps = [],

// counting booleans, numbers and strings
nulls = [],

// counting undefined and null
nans = [],

// counting nans
objs, counts, objects = [],
al = arguments.length;
// quick escapes:
if (al < 2) return true;
var l0 = a.length;
if ([].slice.call(arguments).some(function(s) {
return s.length != l0;
})) return false;

for (var i = 0; i < al; i++) {
var multiset = arguments[i];
maps.push(map = {}); // better: Object.create(null);
objects.push({
vals: objs = [],
count: counts = []
});
nulls[i] = 0;
nans[i] = 0;
for (var j = 0; j < l0; j++) {
var val = multiset[j];
if (val !== val) nans[i]++;
else if (val === null) nulls[i]++;
else if (Object(val) === val) { // non-primitive
var ind = objs.indexOf(val);
if (ind > -1) counts[ind]++;
else objs.push(val), counts.push(1);
} else { // booleans, strings and numbers do compare together
if (typeof val == "boolean") val = +val;
if (val in map) map[val]++;
else map[val] = 1;
}
}
}

// testing if nulls and nans are the same everywhere
for (var i = 1; i < al; i++)
if (nulls[i] != nulls[0] || nans[i] != nans[0]) return false;

// testing if primitives were the same everywhere
var map0 = maps[0];
for (var el in map0)
for (var i = 1; i < al; i++) {
if (map0[el] !== maps[i][el]) return false;
delete maps[i][el];
}
for (var i = 1; i < al; i++)
for (var el in maps[i])
return false;

// testing of objects were the same everywhere
var objs0 = objects[0].vals,
ol = objs0.length;
counts0 = objects[0].count;
for (var i = 1; i < al; i++)
if (objects[i].count.length != ol) return false;
for (var i = 0; i < ol; i++)
for (var j = 1; j < al; j++)
if (objects[j].count[objects[j].vals.indexOf(objs0[i])] != counts0[i]) return false;

// else, the multisets are equal:
return true;
}

function mapTypes(a) {

var _map = {};

for (var i = 0; i < a.length; i++) {

var _t = toType(a[i]);
if (keyGen.hasOwnProperty(_t)) {
_t = _t + keyGen[_t](a[i]);
}

if (!_map[_t]) {
_map[_t] = [];
}

_map[_t].push(i);
}

return _map;
}

function mapTypesMatch(mapA, mapB) {
var a = Object.keys(mapA);
var b = Object.keys(mapB);

if (a.length != b.length) return false;

for (var i = 0; i < a.length; i++) {

if (!mapB[a[i]]) return false;
if (mapA[a[i]].length != mapB[a[i]].length) return false;
}

return true;
}

function arrayElementsMatch(mapA, sourceA, mapB, sourceB) {

for (var key in mapA) {
if (mapA.hasOwnProperty(key)) {

for (var i = 0; i < mapA[key].length; i++) {
var found = false;
for (var k = 0; k < mapB[key].length; k++) {
if (sourceA[mapA[key][i]] === sourceB[mapB[key][k]]) {
mapB[key].slice(k, 1);
found = true;
break;
}
}
if (!found) return false;
}
}
}

return true;
}

var keyGen = {
"[object Object]": function(obj) {
return Object.keys(obj).length;
},
"[object Array]": function(arr) {
return arr.length;
},
"[object Date]": function(d) {
return d.getYear();
},
"[object Number]": function(n) {
return n.toString().length;
},
"[object String]": function(str) {
return str.length;
}
}

var toType = function(obj) {
return Object.prototype.toString.call(obj);
}

function sameElementsBruno2(a, b) {

if (a.length != b.length) return false;

var mapA = mapTypes(a);
var mapB = mapTypes(b);

if (!mapTypesMatch(mapA, mapB)) return false;
if (!arrayElementsMatch(mapA, a, mapB, b)) return false;

return true;
}

function sameElementsBruno3(a, b) {
var objs = [];
// if length is not the same then must not be equal
if (a.length != b.length) return false;

// do an initial sort which will group types
a.sort();
b.sort();

for (var i = 0; i < a.length; i++) {

var aIsPrimitive = isPrimitive(a[i]);
var bIsPrimitive = isPrimitive(b[i]);

// NaN will not equal itself
if (a[i] !== a[i]) {
if (b[i] === b[i]) {
return false;
}
} else if (aIsPrimitive && bIsPrimitive) {

if (a[i] != b[i]) return false;
}
// if not primitive increment the __count property
else if (!aIsPrimitive && !bIsPrimitive) {
incrementCountA(a[i]);
incrementCountB(b[i]);
// keep track on non-primitive objects
objs.push(i);
}
// if both types are not the same then this array
// contains different number of primitives
else {
return false;
}

}

var result = true;

for (var i = 0; i < objs.length; i++) {
var ind = objs[i];
if (a[ind].__aCount !== a[ind].__bCount) result = false;
if (b[ind].__aCount !== b[ind].__bCount) result = false;

// revert object to what it was
// before entering this function
delete a[ind].__aCount;
delete a[ind].__bCount;
delete b[ind].__aCount;
delete b[ind].__bCount;
}

return result;
}

// inspired by @Bergi's code

function isPrimitive(arg) {
return Object(arg) !== arg;
}

function incrementCountA(arg) {
if (arg.hasOwnProperty("__aCount")) {
arg.__aCount = arg.__aCount + 1;
} else {
Object.defineProperty(arg, "__aCount", {
enumerable: false,
value: 1,
writable: true,
configurable: true
});
}
}

function incrementCountB(arg) {
if (arg.hasOwnProperty("__bCount")) {
arg.__bCount = arg.__bCount + 1;
} else {
Object.defineProperty(arg, "__bCount", {
enumerable: false,
value: 1,
writable: true,
configurable: true
});
}
}

function sameElementsTHG(a, b) {
var hash = function(x) {
return typeof x + (typeof x == "object" ? a.indexOf(x) : x);
}

return a.map(hash).sort().join() == b.map(hash).sort().join();
}

Object.defineProperty(Boolean.prototype, "equals", {
enumerable: false,
configurable: true,
value: function(c) {
return this == c;
}
});

Object.defineProperty(Number.prototype, "equals", {
enumerable: false,
configurable: true,
value: Boolean.prototype.equals
});
Object.defineProperty(String.prototype, "equals", {
enumerable: false,
configurable: true,
value: Boolean.prototype.equals
});

Object.defineProperty(Object.prototype, "equals", {
enumerable: false,
configurable: true,
value: function(c, reference) {
if (true === reference) return this === c;
if (typeof this != typeof c) {
return false;
}
var d = [Object.keys(this), Object.keys(c)],
f = d[0].length;
if (f !== d[1].length) {
return false;
}
for (var e = 0; e < f; e++) {
if (d[0][e] != d[1][e] || !this[d[0][e]].equals(c[d[1][e]])) {
return false;
}
}
return true;
}
});
Object.defineProperty(Array.prototype, "equals", {
enumerable: false,
configurable: true,
value: function(c, reference) {

var d = this.length;
if (d != c.length) {
return false;
}
var f = Array.prototype.equals.sort(this.concat());
c = Array.prototype.equals.sort(c.concat(),f)
if (reference) {
for (var e = 0; e < d; e++) {
if (f[e] != c[e]) {
return false;
}
}
} else {
for (var e = 0; e < d; e++) {
if (!f[e].equals(c[e], reference)) {
return false;
}
}
}
return true;

}
});
Object.defineProperty(Array.prototype.equals, "sort", {
enumerable: false,
value: function sort (curr,prev) {
var weight = {
"[object Undefined]":6,
"[object Object]":5,
"[object Null]":4,
"[object String]":3,
"[object Number]":2,
"[object Boolean]":1
}
if (prev) { //mark the objects
for (var i = prev.length,j,t;i>0;i--) {
t = typeof (j = prev[i]);
if (j != null && t === "object") {
j._pos = i;
} else if (t !== "object" && t != "undefined" ) break;
}
}

curr.sort (sorter);

if (prev) {
for (var k = prev.length,l,t;k>0;k--) {
t = typeof (l = prev[k]);
if (t === "object" && l != null) {
delete l._pos;
} else if (t !== "object" && t != "undefined" ) break;
}
}
return curr;

function sorter (a,b) {

var tStr = Object.prototype.toString
var types = [tStr.call(a),tStr.call(b)]
var ret = [0,0];
if (types[0] === types[1] && types[0] === "[object Object]") {
if (prev) return a._pos - b._pos
else {
return a === b ? 0 : 1;
}
} else if (types [0] !== types [1]){
return weight[types[0]] - weight[types[1]]
}

return a>b?1:a<b?-1:0;
}

}

});

function sameElementsGlutamat(c, d , referenceCheck) {
return c.equals(d, referenceCheck);
}

var sameElementsGlutamat2 = (function () {
var f, of, objectFlagName;
of = objectFlagName = "__pos";
var tstr = function (o) {
var t = typeof o;
if (o === null)
t = "null";
return t
};
var types = {};
(function () {
var tmp = {};
Object.defineProperty(types, tstr(1), {
set: function (v) {
if (f)
tmp[v] = -~tmp[v];
else
tmp[v] = ~-tmp[v];
},
get: function () {
var ret = 1;
for (var k in tmp) {
ret &= !tmp[k];
}
tmp = {};
return ret;
}
});
})();
(function () {
var tmp = {};
Object.defineProperty(types, tstr(""), {

set: function (v) {
if (f) {
tmp[v] = -~tmp[v];
} else {

tmp[v] = ~-tmp[v];
}
},
get: function () {
var ret = 1;
for (var k in tmp) {
ret &= !tmp[k];
}
tmp = {};
return ret;
}
});
})();

(function () {
var tmp = [];
tmp.push(v);
if (v[of]===undefined) {
v[of] = [tmp.length -1];
} else {
v[of].push(tmp.length -1)
}

}
Object.defineProperty(types, tstr({}), {
get: function () {
var ret = true;
for (var i = tmp.length - 1; i >= 0; i--) {
var c = tmp[i]
if (typeof c !== "undefined") {
ret = false
delete c[of]
}
}
tmp = [];
return ret;
},
set: function (v) {
var pos;
if (f) {
} else if (!f && (pos = v[of]) !== void 0) {
tmp[pos.pop()] = undefined;
if (pos.length === 0)
delete v[of];
} else {
}
}
});
}());
(function () {
var tmp = 0;
Object.defineProperty(types, tstr(undefined), {
get: function () {
var ret = !tmp;
tmp = 0;
return ret;

},
set: function () {
tmp += f ? 1 : -1;
}
});
})();
(function () {
var tmp = 0;
Object.defineProperty(types, tstr(null), {
get: function () {
var ret = !tmp;
tmp = 0;
return ret;
},
set: function () {
tmp += f ? 1 : -1;
}
});
})();

var tIt = [tstr(1), tstr(""), tstr({}), tstr(undefined), tstr(null)];

return function eq(a, b) {

f = true;
for (var i = a.length - 1; i >= 0; i--) {
var v = a[i];
types[tstr(v)] = v;
}
f = false;
for (var k = b.length - 1; k >= 0; k--) {
var w = b[k];
types[tstr(w)] = w;
}
var r = 1;
for (var l = 0, j; j = tIt[l]; l++) {
r &= types [j]
}

return !!r;
}
})()

function sameElementsNumberGuy(a, b) {
if (a.length !== b.length) {
return false;
}
// random trailing hex digits in the hopes of avoiding name collisions
var objectCountProperty = 'counta2771346634949dec4c5';
var stringCounts = {};
var otherCounts = {};
for (var i = 0; i < a.length; i++) {
var item = a[i];
if (Object(item) === item) {
item[objectCountProperty] |= 0;
item[objectCountProperty]++;
} else {
var counter = (typeof item === 'string' ? stringCounts :
otherCounts);
counter.hasOwnProperty(item) ? (counter[item]++) :
(counter[item] = 1);
}
}
var same = true;
for (var i = 0; i < b.length; i++) {
var item = a[i];
if (Object(item) === item) {
if (item[objectCountProperty]) {
item[objectCountProperty]--;
} else {
same = false;
break;
}
} else {
var counter = (typeof item === 'string' ? stringCounts :
otherCounts);
if (counter.hasOwnProperty(item) && counter[item]) {
counter[item]--;
} else {
same = false;
break;
}
}
}
for (var i = 0; i < a.length; i++) {
delete a[i][objectCountProperty];
}
return same;
}
};
</script>

## Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
BrunoLM
sameElementsBrunoLM(a, b);
pending…
Hamidi
sameElementsHamidi(a, b)
pending…
Bergi
sameElementsBergi(a, b)
pending…
Bruno
sameElementsBruno3(a, b);
pending…
THG
sameElementsTHG(a, b)
pending…
Glutamat (deep check)
sameElementsGlutamat (a,b)
pending…
Glutamat (reference check)
sameElementsGlutamat (a,b,true)
pending…
Glutamat 2
sameElementsGlutamat2 (a,b)
pending…
NumberGuy
sameElementsNumberGuy(a,b)
pending…

## Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page: