sort Javascript array by two numeric fields
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : 0;
});
This code sorts the array by gsize
, smalles开发者_运维知识库t to largest.
How would I change it to sort first by gsize
and then by glow
?
grouperArray.sort(function (a, b) {
return a.gsize - b.gsize || a.glow - b.glow;
});
shorter version
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
if(aSize == bSize)
{
return (aLow < bLow) ? -1 : (aLow > bLow) ? 1 : 0;
}
else
{
return (aSize < bSize) ? -1 : 1;
}
});
grouperArray.sort((a, b) => a.gsize - b.gsize || a.glow - b.glow);
Even shorter version using arrow syntax!
I realize this was asked some time ago, but I thought I would add my solution.
This function generates sort methods dynamically. simply supply each sortable child property name, prepended with +/- to indicate ascending or descending order. Super re-usable, and it doesn't need to know anything about the data structure you've put together. Could be made idiot proof - but doesn't seem necessary.
function getSortMethod(){
var _args = Array.prototype.slice.call(arguments);
return function(a, b){
for(var x in _args){
var ax = a[_args[x].substring(1)];
var bx = b[_args[x].substring(1)];
var cx;
ax = typeof ax == "string" ? ax.toLowerCase() : ax / 1;
bx = typeof bx == "string" ? bx.toLowerCase() : bx / 1;
if(_args[x].substring(0,1) == "-"){cx = ax; ax = bx; bx = cx;}
if(ax != bx){return ax < bx ? -1 : 1;}
}
}
}
example usage:
items.sort(getSortMethod('-price', '+priority', '+name'));
this would sort items
with lowest price
first, with ties going to the item with the highest priority
. further ties are broken by the item name
where items is an array like:
var items = [
{ name: "z - test item", price: "99.99", priority: 0, reviews: 309, rating: 2 },
{ name: "z - test item", price: "1.99", priority: 0, reviews: 11, rating: 0.5 },
{ name: "y - test item", price: "99.99", priority: 1, reviews: 99, rating: 1 },
{ name: "y - test item", price: "0", priority: 1, reviews: 394, rating: 3.5 },
{ name: "x - test item", price: "0", priority: 2, reviews: 249, rating: 0.5 } ...
];
live demo: http://gregtaff.com/misc/multi_field_sort/
EDIT: Fixed issue with Chrome.
I expect the ternary operator ((aSize < bSize) ? -1 : (aSize > bSize) ? 1 : 0;)
has you confused. You should check out the link to understand it better.
Until then, here's your code blown out into full if/else.
grouperArray.sort(function (a, b) {
if (a.gsize < b.gsize)
{
return -1;
}
else if (a.gsize > b.gsize)
{
return 1;
}
else
{
if (a.glow < b.glow)
{
return -1;
}
else if (a.glow > b.glow)
{
return 1;
}
return 0;
}
});
Here's an implementation for those who may want something more generic that would work with any number of fields.
Array.prototype.sortBy = function (propertyName, sortDirection) {
var sortArguments = arguments;
this.sort(function (objA, objB) {
var result = 0;
for (var argIndex = 0; argIndex < sortArguments.length && result === 0; argIndex += 2) {
var propertyName = sortArguments[argIndex];
result = (objA[propertyName] < objB[propertyName]) ? -1 : (objA[propertyName] > objB[propertyName]) ? 1 : 0;
//Reverse if sort order is false (DESC)
result *= !sortArguments[argIndex + 1] ? 1 : -1;
}
return result;
});
}
Basically, you may specify any number of property name / sort direction:
var arr = [{
LastName: "Doe",
FirstName: "John",
Age: 28
}, {
LastName: "Doe",
FirstName: "Jane",
Age: 28
}, {
LastName: "Foo",
FirstName: "John",
Age: 30
}];
arr.sortBy("LastName", true, "FirstName", true, "Age", false);
//Will return Jane Doe / John Doe / John Foo
arr.sortBy("Age", false, "LastName", true, "FirstName", false);
//Will return John Foo / John Doe / Jane Doe
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : ( (aLow < bLow ) ? -1 : (aLow > bLow ) ? 1 : 0 );
});
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : (aLow < bLow) ? -1 : (aLow > bLow) ? 1 : 0); });
Here is an implementation that uses recursion to sort by any number of sort fields from 1 to infinite. You pass it a results array which is an array of result objects to sort, and a sorts array which is an array of sort objects defining the sort. Each sort object must have a "select" key for the key name that it sorts by and an "order" key which is a string indicating "ascending" or "descending".
sortMultiCompare = (a, b, sorts) => {
let select = sorts[0].select
let order = sorts[0].order
if (a[select] < b[select]) {
return order == 'ascending' ? -1 : 1
}
if (a[select] > b[select]) {
return order == 'ascending' ? 1 : -1
}
if(sorts.length > 1) {
let remainingSorts = sorts.slice(1)
return this.sortMultiCompare(a, b, remainingSorts)
}
return 0
}
sortResults = (results, sorts) => {
return results.sort((a, b) => {
return this.sortMultiCompare(a, b, sorts)
})
}
// example inputs
const results = [
{
"LastName": "Doe",
"FirstName": "John",
"MiddleName": "Bill"
},
{
"LastName": "Doe",
"FirstName": "Jane",
"MiddleName": "Bill"
},
{
"LastName": "Johnson",
"FirstName": "Kevin",
"MiddleName": "Bill"
}
]
const sorts = [
{
"select": "LastName",
"order": "ascending"
},
{
"select": "FirstName",
"order": "ascending"
},
{
"select": "MiddleName",
"order": "ascending"
}
]
// call the function like this:
let sortedResults = sortResults(results, sorts)
A dynamic way to do that with MULTIPLE keys:
- filter unique values from each col/key of sort
- put in order or reverse it
- add weights width zeropad for each object based on indexOf(value) keys values
- sort using caclutated weights
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}
]);
This is what I use
function sort(a, b) {
var _a = "".concat(a.size, a.glow);
var _b = "".concat(b.size, b.glow);
return _a < _b;
}
concat the two items as a string and they will be sorted by a string value. If you want you could wrap _a and _b with parseInt to compare them as numbers if you know they will be numerical.
Here is the solution for the case, when you have a priority sort key, which might not exist in some particular items, so you have to sort by fallback keys.
An input data example (id2 is priority sort key):
const arr = [
{id: 1},
{id: 2, id2: 3},
{id: 4},
{id: 3},
{id: 10, id2: 2},
{id: 7},
{id: 6, id2: 1},
{id: 5},
{id: 9, id2: 2},
{id: 8},
];
And the output should be:
[ { id: 6, id2: 1 },
{ id: 9, id2: 2 },
{ id: 10, id2: 2 },
{ id: 2, id2: 3 },
{ id: 1 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 7 },
{ id: 8 } ]
The comparator function will be like:
arr.sort((a,b) => {
if(a.id2 || b.id2) {
if(a.id2 && b.id2) {
if(a.id2 === b.id2) {
return a.id - b.id;
}
return a.id2 - b.id2;
}
return a.id2 ? -1 : 1;
}
return a.id - b.id
});
P.S. In case if .id of .id2 can be zeros, consider to use typeof
.
Let's simplify.
Say you have an array of arrays:
let tmp = [
[0, 1],
[2, 1],
[1, 1],
[0, 0],
[2, 0],
[1, 0],
[0, 2],
[2, 2],
[1, 2],
]
Executing:
tmp.sort((a, b) => {
if (a[1] != b[1])
return a[1] - b[1];
else
return a[0] - b[0];
})
Will yield:
[
[0, 0],
[1, 0],
[2, 0],
[0, 1],
[1, 1],
[2, 1],
[0, 2],
[1, 2],
[2, 2]
]
var items = [
{ name: "z - test item", price: "99.99", priority: 0, reviews: 309, rating: 2 },
{ name: "z - test item", price: "1.99", priority: 0, reviews: 11, rating: 0.5 },
{ name: "y - test item", price: "99.99", priority: 1, reviews: 99, rating: 1 },
{ name: "y - test item", price: "0", priority: 1, reviews: 394, rating: 3.5 },
{ name: "x - test item", price: "0", priority: 2, reviews: 249, rating: 0.5 }];
items.sort(function (a, b) {
var nameA = a.name.toUpperCase();
var nameB = b.name.toUpperCase();
var nameC = a.price.toUpperCase();
var nameD = b.price.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB || nameC > nameD) {
return 1;
}
// names must be equal
return 0;
});`
grouperArray.sort(
function(a,b){return a.gsize == b.gsize ? a.glow - b.glow : a.gsize - b.gsize}
);
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
if (aSize !== aSize)
return aSize - bSize;
return a.glow - b.glow;
});
not tested, but I think that should work.
In my case, i sort notification list by param 'important' and by 'date'
step 1: i filter notifications by 'important' and unImportant
let importantNotifications = notifications.filter( (notification) => notification.isImportant); let unImportantNotifications = notifications.filter( (notification) => !notification.isImportant);
step 2: i sort them by date
sortByDate = (notifications) => { return notifications.sort((notificationOne, notificationTwo) => { return notificationOne.date - notificationTwo.date; }); };
step 3: merge them
[ ...this.sortByDate(importantNotifications), ...this.sortByDate(unImportantNotifications), ];
If you're happy to use the new tidy.js package you can achieve this with
tidy(input_array,
arrange(['var1', desc('var2')])
);
Besides the other answers here I got inconsistent data on my arrays where 1 wanted a primary ASC sort on field x and a secondary DESC sort on field y.
The solution is in giving the primary sort more importance by multiplying the number with lets say 1000000000
arrayOfObjects.sort((a, b) => {
return (
// Multiply by a high number to the most important sort, that makes them heavier than the second sort
// First sort ASC (notice the - minus in the end instead of the || in other answers !)
(a.paramX * 1000000000) -
(b.paramX * 1000000000) -
// Second sort DESC (switch them if you want ASC too)
(a.paramY - b.paramY)
)
})
for sorting on multiple dates on the object it is this:
// param date1 ASC and param date2 DESC
arrayOfObjects.sort((a, b) => {
return (
(a.date1.getTime() * 1000000000) -
(b.date1.getTime() * 1000000000) -
(a.date2.getTime() - b.date2.getTime())
)
})
精彩评论