I have an array that contains raw values as well as computed values. I would like to be able to sort the array dynamically based on either a raw value or the result of one of the computed values. The actual sorts that will be required will not be known until runtime.
I've put together the below sample (plunker here) that demonstrates the situation and a working solution*. I would like to know how to improve this... specifically, the use of:
Array.prototype.sortBy = function (property) {
return this.sort(mySort(property));
};
is copied from this stackoverflow response - and Ege Özcan specifically states
//Please don't just copy-paste this code.
//See the explanation at the end. A lot could break.
I would like to understand how to implement this sorting algorithm on my object without violating the 'A lot could break' warning (that I don't understand).
*One of the things I love about stackoverflow is that the process of framing the question well frequently leads you to simplify the problem to the point where a (not necessarily the) solution presents itself. I started this problem not being able to sort based on a property or computed value. Now, I'm looking for validation/improvement on the implementation.
Sample:
var rawData = [
{ "Id": 3, "itemCount": 3531, "val1": 905, "val2": 172 },
{ "Id": 2, "itemCount": 3111, "val1": 799, "val2": 147 },
{ "Id": 4, "itemCount": 3411, "val1": 871, "val2": 199 },
{ "Id": 5, "itemCount": 3414, "val1": 892, "val2": 178 },
{ "Id": 1, "itemCount": 3182, "val1": 845, "val2": 155 }
];
function MyItem(item) {
var self = this;
for (var val in item) {
if (item.hasOwnProperty(val)) {
self[val] = item[val];
}
}
}
function extendMyItems() {
MyItem.prototype.computedOne = function () {
var input = this;
return input.itemCount / input.val1;
};
MyItem.prototype.computedTwo = function () {
var input = this;
return input.val1 * input.val2;
};
}
function getItems(input) {
var ret = [];
for (var i = 0; i < input.length; i++) {
var item = new MyItem(input[i]);
ret.push(item);
}
return ret;
}
function doIt() {
Array.prototype.sortBy = function (property) {
return this.sort(mySort(property));
};
extendMyItems();
var sortList = [{ "sortKey": "Id", "sortOrder": "asc" },
{ "sortKey": "val1", "sortOrder": "asc" },
{ "sortKey": "val2", "sortOrder": "desc" },
{ "sortKey": "computedOne", "sortOrder": "desc", "isComputed": true },
{ "sortKey": "Id", "sortOrder": "desc" },
{ "sortKey": "computedTwo", "sortOrder": "asc", "isComputed": true }];
// get the array of MyItem
var myItems = getItems(rawData);
for (var k = 0; k < sortList.length; k++) {
myItems.sortBy(sortList[k]);
// process the sorted items here (ranking/scoring, etc)
for (var p = 0; p < myItems.length; p++) {
console.log('Id: ' + myItems[p].Id + ' val1: ' + myItems[p].val1 + ' val2: ' + myItems[p].val2 + ' c1: ' + myItems[p].computedOne() + ' c2: ' + myItems[p].computedTwo());
}
}
function mySort(srt) {
var so = srt.sortOrder == 'asc' ? 1 : -1;
var key = srt.sortKey;
var result = 0;
console.log(srt.sortKey + ' ' + srt.sortOrder + ':');
return function (a, b) {
if (srt.isComputed) {
// this seems like a hack - is there a better way to switch between property and function value????
result = (a[key]() < b[key]()) ? -1 : (a[key]() > b[key]()) ? 1 : 0;
} else {
result = (a[key] < b[key]) ? -1 : (a[key] > b[key]) ? 1 : 0;
}
return result * so;
};
}
}