From this original question, how would I apply a sort on multiple fields?
Using this slightly adapted structure, how would I sort city (ascending) & then price (
function sort(data, orderBy) {
orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
return data.sort((a, b) => {
for (let i = 0, size = orderBy.length; i < size; i++) {
const key = Object.keys(orderBy[i])[0],
o = orderBy[i][key],
valueA = a[key],
valueB = b[key];
if (!(valueA || valueB)) {
console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
return [];
}
if (+valueA === +valueA) {
return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
} else {
if (valueA.localeCompare(valueB) > 0) {
return o.toLowerCase() === 'desc' ? -1 : 1;
} else if (valueA.localeCompare(valueB) < 0) {
return o.toLowerCase() === 'desc' ? 1 : -1;
}
}
}
});
}
Using :
sort(homes, [{city : 'asc'}, {price: 'desc'}])
var homes = [
{"h_id":"3",
"city":"Dallas",
"state":"TX",
"zip":"75201",
"price":"162500"},
{"h_id":"4",
"city":"Bevery Hills",
"state":"CA",
"zip":"90210",
"price":"319250"},
{"h_id":"6",
"city":"Dallas",
"state":"TX",
"zip":"75000",
"price":"556699"},
{"h_id":"5",
"city":"New York",
"state":"NY",
"zip":"00010",
"price":"962500"}
];
function sort(data, orderBy) {
orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
return data.sort((a, b) => {
for (let i = 0, size = orderBy.length; i < size; i++) {
const key = Object.keys(orderBy[i])[0],
o = orderBy[i][key],
valueA = a[key],
valueB = b[key];
if (!(valueA || valueB)) {
console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
return [];
}
if (+valueA === +valueA) {
return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
} else {
if (valueA.localeCompare(valueB) > 0) {
return o.toLowerCase() === 'desc' ? -1 : 1;
} else if (valueA.localeCompare(valueB) < 0) {
return o.toLowerCase() === 'desc' ? 1 : -1;
}
}
}
});
}
console.log(sort(homes, [{city : 'asc'}, {price: 'desc'}]));
The following function will allow you to sort an array of objects on one or multiple properties, either ascending (default) or descending on each property, and allow you to choose whether or not to perform case sensitive comparisons. By default, this function performs case insensitive sorts.
The first argument must be the array containing the objects.
The subsequent argument(s) must be a comma separated list of strings that reference the different object properties to sort by. The last argument (which is optional) is a boolean to choose whether or not to perform case sensitive sorts - use true
for case sensitive sorts.
The function will sort each property/key in ascending order by default. If you want a particular key to sort in descending order, then instead pass in an array in this format: ['property_name', true]
.
Here are some sample uses of the function followed by an explanation (where homes
is an array containing the objects):
objSort(homes, 'city')
--> sort by city (ascending, case in-sensitive)
objSort(homes, ['city', true])
--> sort by city (descending, case in-sensitive)
objSort(homes, 'city', true)
--> sort by city then price (ascending, case sensitive)
objSort(homes, 'city', 'price')
--> sort by city then price (both ascending, case in-sensitive)
objSort(homes, 'city', ['price', true])
--> sort by city (ascending) then price (descending), case in-sensitive)
And without further ado, here's the function:
function objSort() {
var args = arguments,
array = args[0],
case_sensitive, keys_length, key, desc, a, b, i;
if (typeof arguments[arguments.length - 1] === 'boolean') {
case_sensitive = arguments[arguments.length - 1];
keys_length = arguments.length - 1;
} else {
case_sensitive = false;
keys_length = arguments.length;
}
return array.sort(function (obj1, obj2) {
for (i = 1; i < keys_length; i++) {
key = args[i];
if (typeof key !== 'string') {
desc = key[1];
key = key[0];
a = obj1[args[i][0]];
b = obj2[args[i][0]];
} else {
desc = false;
a = obj1[args[i]];
b = obj2[args[i]];
}
if (case_sensitive === false && typeof a === 'string') {
a = a.toLowerCase();
b = b.toLowerCase();
}
if (! desc) {
if (a < b) return -1;
if (a > b) return 1;
} else {
if (a > b) return -1;
if (a < b) return 1;
}
}
return 0;
});
} //end of objSort() function
And here's some sample data:
var homes = [{
"h_id": "3",
"city": "Dallas",
"state": "TX",
"zip": "75201",
"price": 162500
}, {
"h_id": "4",
"city": "Bevery Hills",
"state": "CA",
"zip": "90210",
"price": 1000000
}, {
"h_id": "5",
"city": "new york",
"state": "NY",
"zip": "00010",
"price": 1000000
}, {
"h_id": "6",
"city": "Dallas",
"state": "TX",
"zip": "85000",
"price": 300000
}, {
"h_id": "7",
"city": "New York",
"state": "NY",
"zip": "00020",
"price": 345000
}];
Here's another one that's perhaps closer to your idea for the syntax
function sortObjects(objArray, properties /*, primers*/) {
var primers = arguments[2] || {}; // primers are optional
properties = properties.map(function(prop) {
if( !(prop instanceof Array) ) {
prop = [prop, 'asc']
}
if( prop[1].toLowerCase() == 'desc' ) {
prop[1] = -1;
} else {
prop[1] = 1;
}
return prop;
});
function valueCmp(x, y) {
return x > y ? 1 : x < y ? -1 : 0;
}
function arrayCmp(a, b) {
var arr1 = [], arr2 = [];
properties.forEach(function(prop) {
var aValue = a[prop[0]],
bValue = b[prop[0]];
if( typeof primers[prop[0]] != 'undefined' ) {
aValue = primers[prop[0]](aValue);
bValue = primers[prop[0]](bValue);
}
arr1.push( prop[1] * valueCmp(aValue, bValue) );
arr2.push( prop[1] * valueCmp(bValue, aValue) );
});
return arr1 < arr2 ? -1 : 1;
}
objArray.sort(function(a, b) {
return arrayCmp(a, b);
});
}
// just for fun use this to reverse the city name when sorting
function demoPrimer(str) {
return str.split('').reverse().join('');
}
// Example
sortObjects(homes, ['city', ['price', 'desc']], {city: demoPrimer});
Demo: http://jsfiddle.net/Nq4dk/2/
Edit: Just for fun, here's a variation that just takes an sql-like string, so you can do sortObjects(homes, "city, price desc")
function sortObjects(objArray, properties /*, primers*/) {
var primers = arguments[2] || {};
properties = properties.split(/\s*,\s*/).map(function(prop) {
prop = prop.match(/^([^\s]+)(\s*desc)?/i);
if( prop[2] && prop[2].toLowerCase() === 'desc' ) {
return [prop[1] , -1];
} else {
return [prop[1] , 1];
}
});
function valueCmp(x, y) {
return x > y ? 1 : x < y ? -1 : 0;
}
function arrayCmp(a, b) {
var arr1 = [], arr2 = [];
properties.forEach(function(prop) {
var aValue = a[prop[0]],
bValue = b[prop[0]];
if( typeof primers[prop[0]] != 'undefined' ) {
aValue = primers[prop[0]](aValue);
bValue = primers[prop[0]](bValue);
}
arr1.push( prop[1] * valueCmp(aValue, bValue) );
arr2.push( prop[1] * valueCmp(bValue, aValue) );
});
return arr1 < arr2 ? -1 : 1;
}
objArray.sort(function(a, b) {
return arrayCmp(a, b);
});
}
Here is mine for your reference, with example:
function msort(arr, ...compFns) {
let fn = compFns[0];
arr = [].concat(arr);
let arr1 = [];
while (arr.length > 0) {
let arr2 = arr.splice(0, 1);
for (let i = arr.length; i > 0;) {
if (fn(arr2[0], arr[--i]) === 0) {
arr2 = arr2.concat(arr.splice(i, 1));
}
}
arr1.push(arr2);
}
arr1.sort(function (a, b) {
return fn(a[0], b[0]);
});
compFns = compFns.slice(1);
let res = [];
arr1.map(a1 => {
if (compFns.length > 0) a1 = msort(a1, ...compFns);
a1.map(a2 => res.push(a2));
});
return res;
}
let tstArr = [{ id: 1, sex: 'o' }, { id: 2, sex: 'm' }, { id: 3, sex: 'm' }, { id: 4, sex: 'f' }, { id: 5, sex: 'm' }, { id: 6, sex: 'o' }, { id: 7, sex: 'f' }];
function tstFn1(a, b) {
if (a.sex > b.sex) return 1;
else if (a.sex < b.sex) return -1;
return 0;
}
function tstFn2(a, b) {
if (a.id > b.id) return -1;
else if (a.id < b.id) return 1;
return 0;
}
console.log(JSON.stringify(msort(tstArr, tstFn1, tstFn2)));
//output:
//[{"id":7,"sex":"f"},{"id":4,"sex":"f"},{"id":5,"sex":"m"},{"id":3,"sex":"m"},{"id":2,"sex":"m"},{"id":6,"sex":"o"},{"id":1,"sex":"o"}]
A dynamic way to do that with MULTIPLE keys:
Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) {
sorts.map(sort => {
sort.uniques = Array.from(
new Set(this.map(obj => obj[sort.key]))
);
sort.uniques = sort.uniques.sort((a, b) => {
if (typeof a == 'string') {
return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
}
else if (typeof a == 'number') {
return sort.inverse ? (a < b) : (a > b ? 1 : 0);
}
else if (typeof a == 'boolean') {
let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
return x;
}
return 0;
});
});
const weightOfObject = (obj) => {
let weight = "";
sorts.map(sort => {
let zeropad = `${sort.uniques.length}`.length;
weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
});
//obj.weight = weight; // if you need to see weights
return weight;
}
this.sort((a, b) => {
return weightOfObject(a).localeCompare( weightOfObject(b) );
});
return this;
}
});
Use:
// works with string, number and boolean
let sortered = your_array.orderBy([
{key: "type", inverse: false},
{key: "title", inverse: false},
{key: "spot", inverse: false},
{key: "internal", inverse: true}
]);
I think this may be the easiest way to do it.
https://coderwall.com/p/ebqhca/javascript-sort-by-two-fields
It's really simple and I tried it with 3 different key value pairs and it worked great.
Here is a simple example, look at the link for more details
testSort(data) {
return data.sort(
a['nameOne'] > b['nameOne'] ? 1
: b['nameOne'] > a['nameOne'] ? -1 : 0 ||
a['date'] > b['date'] ||
a['number'] - b['number']
);
}