I managed to implement this method:
I need to get a hash code from objects.
Object.prototype.GetHashCode = function () { var s = this instanceof Object ? stringify(this) : this.toString(); var hash = 0; if (s.length === 0) return hash; for (var i = 0; i < s.length; ++i) { hash = ((hash << 5) - hash) + s.charCodeAt(i); } return hash; }; Number.prototype.GetHashCode = function () { return this.valueOf(); };
Since JSON.stringify will fail with circular references, I created another method for formatting it so that I can make the most of the object as a string and calculate the hash code above it like this:
function isPlainObject(obj) { if ((typeof (obj) !== "object" || obj.nodeType || (obj instanceof Window)) || (obj.constructor && !({}).hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) ) { return false; } return true; } function stringify(obj, s) { s = s || ""; for (var i in obj) { var o = obj[i]; if (o && (o instanceof Array || isPlainObject(o))) { s += i + ":" + JSON.stringify(o); } else if (o && typeof o === "object") { s += i + ":" + "$ref#" + o; } else { s += i + ":" + o; } } return s; }
Performance impact is small. For a large object, this is one and the same for small objects, it loses, but it is still pretty fast and safe. Performance test here .
Name op/s --------------------------------- JSON.stringify large 62 stringify large 62 JSON.stringify small 1,690,183 stringify small 1,062,452
My GroupBy Method
function GroupBy(a, keySelector, elementSelector, comparer) { // set default values for opitinal parameters elementSelector = elementSelector || function(e) { return e; }; comparer = comparer || { Equals: function(a,b) { return a==b }, GetHashCode: function(e) { return e.GetHashCode(); } }; var key, hashKey, reHashKey; // keep groups separated by hash var hashs = {}; for (var i = 0, n = a.length; i < n; ++i) { // in case of same hash, but Equals returns false reHashKey = undefined; // grabs the key key = keySelector(a[i]); // grabs the hashcode hashKey = comparer.GetHashCode(key); // if a hash exists in the list // compare values with Equals // in case it return false, generate a unique hash if (typeof hashs[hashKey] !== "undefined") reHashKey = comparer.Equals(key, hashs[hashKey].Key) ? hashKey : hashKey + " " + i; // if a new hash has been generated, update if (typeof reHashKey !== "undefined" && reHashKey !== hashKey) hashKey = reHashKey; // get/create a new group and add the current element to the list hashs[hashKey] = hashs[hashKey] || { Key: key, Elements: [] }; hashs[hashKey].Elements.push(a[i]); } return hashs; }
To check
var arrComplex = [ { N: { Value: 10 }, Name: "Foo" }, { N: { Value: 10 }, Name: "Bar" }, { N: { Value: 20 }, Name: "Foo" }, { N: { Value: 20 }, Name: "Bar" } ]; // var x = GroupBy(arrComplex , function(e) { return eN; } , function(e) { return e.Name; } , { Equals: function(a,b) { return a.Value == b.Value }, GetHashCode: function(e) { return e.GetHashCode(); } } ); // console.log(x);
JsFiddle example , now with Jedi.
But, according to my tests , my implementation of GroupBy slower than linq.js GroupBy . This happens faster when I convert ToArray() . Maybe linq.js really only executes when converted to an array, so the difference is that I'm not sure about this part.
Test results
Name op/s --------------------------------- GroupBy 163,261 GroupByToArray 152,382 linq.js groupBy 243,547 linq.js groupBy toArray 26,309