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 (
How about this simple solution:
const sortCompareByCityPrice = (a, b) => {
let comparison = 0
// sort by first criteria
if (a.city > b.city) {
comparison = 1
}
else if (a.city < b.city) {
comparison = -1
}
// If still 0 then sort by second criteria descending
if (comparison === 0) {
if (parseInt(a.price) > parseInt(b.price)) {
comparison = -1
}
else if (parseInt(a.price) < parseInt(b.price)) {
comparison = 1
}
}
return comparison
}
Based on this question javascript sort array by multiple (number) fields
This is a recursive algorithm to sort by multiple fields while having the chance to format values before comparison.
var data = [
{
"id": 1,
"ship": null,
"product": "Orange",
"quantity": 7,
"price": 92.08,
"discount": 0
},
{
"id": 2,
"ship": "2017-06-14T23:00:00.000Z".toDate(),
"product": "Apple",
"quantity": 22,
"price": 184.16,
"discount": 0
},
...
]
var sorts = ["product", "quantity", "ship"]
// comp_val formats values and protects against comparing nulls/undefines
// type() just returns the variable constructor
// String.lower just converts the string to lowercase.
// String.toDate custom fn to convert strings to Date
function comp_val(value){
if (value==null || value==undefined) return null
var cls = type(value)
switch (cls){
case String:
return value.lower()
}
return value
}
function compare(a, b, i){
i = i || 0
var prop = sorts[i]
var va = comp_val(a[prop])
var vb = comp_val(b[prop])
// handle what to do when both or any values are null
if (va == null || vb == null) return true
if ((i < sorts.length-1) && (va == vb)) {
return compare(a, b, i+1)
}
return va > vb
}
var d = data.sort(compare);
console.log(d);
If a and b are equal it will just try the next field until none is available.
Simpler one:
var someArray = [...];
function generateSortFn(props) {
return function (a, b) {
for (var i = 0; i < props.length; i++) {
var prop = props[i];
var name = prop.name;
var reverse = prop.reverse;
if (a[name] < b[name])
return reverse ? 1 : -1;
if (a[name] > b[name])
return reverse ? -1 : 1;
}
return 0;
};
};
someArray.sort(generateSortFn([{name: 'prop1', reverse: true}, {name: 'prop2'}]));
Here is a generic version of @Snowburnt's solution:
var sortarray = [{field:'city', direction:'asc'}, {field:'price', direction:'desc'}];
array.sort(function(a,b){
for(var i=0; i<sortarray.length; i++){
retval = a[sortarray[i].field] < b[sortarray[i].field] ? -1 : a[sortarray[i].field] > b[sortarray[i].field] ? 1 : 0;
if (sortarray[i].direction == "desc") {
retval = retval * -1;
}
if (retval !== 0) {
return retval;
}
}
}
})
This is based on a sort routine I'm using. I didn't test this specific code so it may have errors but you get the idea. The idea is to sort based on the first field that indicates a difference and then stop and go to the next record. So, if you're sorting by three fields and the first field in the compare is enough to determine the sort order of the two records being sorted then return that sort result and go to the next record.
I tested it (actually with a little more complex sort logic) on 5000 records and it did it in the blink of an eye. If you're actually loading more than 1000 records to the client you should probably be using sever-side sorting and filtering.
This code isn't handling case-sensitivity but I leave it to the reader to handle this trivial modification.
I like SnowBurnt's approach but it needs a tweak to test for equivalence on city NOT a difference.
homes.sort(
function(a,b){
if (a.city==b.city){
return (b.price-a.price);
} else {
return (a.city-b.city);
}
});
Here's my solution based on the Schwartzian transform idiom, hope you find it useful.
function sortByAttribute(array, ...attrs) {
// generate an array of predicate-objects contains
// property getter, and descending indicator
let predicates = attrs.map(pred => {
let descending = pred.charAt(0) === '-' ? -1 : 1;
pred = pred.replace(/^-/, '');
return {
getter: o => o[pred],
descend: descending
};
});
// schwartzian transform idiom implementation. aka: "decorate-sort-undecorate"
return array.map(item => {
return {
src: item,
compareValues: predicates.map(predicate => predicate.getter(item))
};
})
.sort((o1, o2) => {
let i = -1, result = 0;
while (++i < predicates.length) {
if (o1.compareValues[i] < o2.compareValues[i]) result = -1;
if (o1.compareValues[i] > o2.compareValues[i]) result = 1;
if (result *= predicates[i].descend) break;
}
return result;
})
.map(item => item.src);
}
Here's an example how to use it:
let games = [
{ name: 'Pako', rating: 4.21 },
{ name: 'Hill Climb Racing', rating: 3.88 },
{ name: 'Angry Birds Space', rating: 3.88 },
{ name: 'Badland', rating: 4.33 }
];
// sort by one attribute
console.log(sortByAttribute(games, 'name'));
// sort by mupltiple attributes
console.log(sortByAttribute(games, '-rating', 'name'));