How do you sort an array on multiple columns?
Solution 1
If owner names differ, sort by them. Otherwise, use publication name for tiebreaker.
function mysortfunction(a, b) {
var o1 = a[3].toLowerCase();
var o2 = b[3].toLowerCase();
var p1 = a[1].toLowerCase();
var p2 = b[1].toLowerCase();
if (o1 < o2) return -1;
if (o1 > o2) return 1;
if (p1 < p2) return -1;
if (p1 > p2) return 1;
return 0;
}
Solution 2
I think what you're looking for is thenBy.js: https://github.com/Teun/thenBy.js
It allows you to use the standard Array.sort, but with firstBy().thenBy().thenBy()
style.
Solution 3
A good way to sort on many fields that are strings is to use toLocaleCompare
and the boolean operator ||
.
Something like:
// Sorting record releases by name and then by title.
releases.sort((oldRelease, newRelease) => {
const compareName = oldRelease.name.localeCompare(newRelease.name);
const compareTitle = oldRelease.title.localeCompare(newRelease.title);
return compareName || compareTitle;
})
If you wanted to sort on more fields, you could simply chain them off the return statement with more boolean operators.
Solution 4
Came across a need to do SQL-style mixed asc and desc object array sorts by keys.
kennebec's solution above helped me get to this:
Array.prototype.keySort = function(keys) {
keys = keys || {};
// via
// https://stackoverflow.com/questions/5223/length-of-javascript-object-ie-associative-array
var obLen = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key))
size++;
}
return size;
};
// avoiding using Object.keys because I guess did it have IE8 issues?
// else var obIx = function(obj, ix){ return Object.keys(obj)[ix]; } or
// whatever
var obIx = function(obj, ix) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (size == ix)
return key;
size++;
}
}
return false;
};
var keySort = function(a, b, d) {
d = d !== null ? d : 1;
// a = a.toLowerCase(); // this breaks numbers
// b = b.toLowerCase();
if (a == b)
return 0;
return a > b ? 1 * d : -1 * d;
};
var KL = obLen(keys);
if (!KL)
return this.sort(keySort);
for ( var k in keys) {
// asc unless desc or skip
keys[k] =
keys[k] == 'desc' || keys[k] == -1 ? -1
: (keys[k] == 'skip' || keys[k] === 0 ? 0
: 1);
}
this.sort(function(a, b) {
var sorted = 0, ix = 0;
while (sorted === 0 && ix < KL) {
var k = obIx(keys, ix);
if (k) {
var dir = keys[k];
sorted = keySort(a[k], b[k], dir);
ix++;
}
}
return sorted;
});
return this;
};
sample usage:
var obja = [
{USER:"bob", SCORE:2000, TIME:32, AGE:16, COUNTRY:"US"},
{USER:"jane", SCORE:4000, TIME:35, AGE:16, COUNTRY:"DE"},
{USER:"tim", SCORE:1000, TIME:30, AGE:17, COUNTRY:"UK"},
{USER:"mary", SCORE:1500, TIME:31, AGE:19, COUNTRY:"PL"},
{USER:"joe", SCORE:2500, TIME:33, AGE:18, COUNTRY:"US"},
{USER:"sally", SCORE:2000, TIME:30, AGE:16, COUNTRY:"CA"},
{USER:"yuri", SCORE:3000, TIME:34, AGE:19, COUNTRY:"RU"},
{USER:"anita", SCORE:2500, TIME:32, AGE:17, COUNTRY:"LV"},
{USER:"mark", SCORE:2000, TIME:30, AGE:18, COUNTRY:"DE"},
{USER:"amy", SCORE:1500, TIME:29, AGE:19, COUNTRY:"UK"}
];
var sorto = {
SCORE:"desc",TIME:"asc", AGE:"asc"
};
obja.keySort(sorto);
yields the following:
0: { USER: jane; SCORE: 4000; TIME: 35; AGE: 16; COUNTRY: DE; }
1: { USER: yuri; SCORE: 3000; TIME: 34; AGE: 19; COUNTRY: RU; }
2: { USER: anita; SCORE: 2500; TIME: 32; AGE: 17; COUNTRY: LV; }
3: { USER: joe; SCORE: 2500; TIME: 33; AGE: 18; COUNTRY: US; }
4: { USER: sally; SCORE: 2000; TIME: 30; AGE: 16; COUNTRY: CA; }
5: { USER: mark; SCORE: 2000; TIME: 30; AGE: 18; COUNTRY: DE; }
6: { USER: bob; SCORE: 2000; TIME: 32; AGE: 16; COUNTRY: US; }
7: { USER: amy; SCORE: 1500; TIME: 29; AGE: 19; COUNTRY: UK; }
8: { USER: mary; SCORE: 1500; TIME: 31; AGE: 19; COUNTRY: PL; }
9: { USER: tim; SCORE: 1000; TIME: 30; AGE: 17; COUNTRY: UK; }
keySort: { }
(using a print function from here)
edit: cleaned up and posted as mksort.js on github.
Solution 5
This is handy for alpha sorts of all sizes. Pass it the indexes you want to sort by, in order, as arguments.
Array.prototype.deepSortAlpha= function(){
var itm, L=arguments.length, order=arguments;
var alphaSort= function(a, b){
a= a.toLowerCase();
b= b.toLowerCase();
if(a== b) return 0;
return a> b? 1:-1;
}
if(!L) return this.sort(alphaSort);
this.sort(function(a, b){
var tem= 0, indx=0;
while(tem==0 && indx<L){
itm=order[indx];
tem= alphaSort(a[itm], b[itm]);
indx+=1;
}
return tem;
});
return this;
}
var arr= [[ "Nilesh","Karmshil"], ["Pranjal","Deka"], ["Susants","Ghosh"],
["Shiv","Shankar"], ["Javid","Ghosh"], ["Shaher","Banu"], ["Javid","Rashid"]];
arr.deepSortAlpha(1,0);
flavour404
Updated on January 16, 2022Comments
-
flavour404 over 2 years
I have a multidimensional array. The primary array is an array of
[publicationID][publication_name][ownderID][owner_name]
What I am trying to do is sort the array by
owner_name
and then bypublication_name
. I know in JavaScript you haveArray.sort()
, into which you can put a custom function, in my case i have:function mysortfunction(a, b) { var x = a[3].toLowerCase(); var y = b[3].toLowerCase(); return ((x < y) ? -1 : ((x > y) ? 1 : 0)); }
This is fine for just sorting on the one column, namely owner_name, but how do I modify it to sort on
owner_name
, thenpublication_name
? -
defau1t almost 11 yearsMay I know from where did you collect this data [[ "Nilesh","Karmshil"], ["Pranjal","Deka"], ["Susants","Ghosh"], ["Shiv","Shankar"], ["Javid","Ghosh"], ["Shaher","Banu"], ["Javid","Rashid"]];
-
Bla... about 9 years@dcp I don't see how it could also sort the second attribute. Unless you loop it as many as the number of selected columns.. Am I right? e.g.
[[A, 10], [J, 15], [A, 5], [J, 5]] => [[A, 10], [A, 5], [J, 15], [J, 5]]
-
dcp about 9 years@user26409021 - No, that's not right. It would end up being [[A, 5], [A, 10], [J, 5], [J, 15]]. It sorts by the first attribute first, and if those are the same, then it sorts by the second attribute. So in your example, A would come before J. In the case where A is the same for two elements, then it would use the second attribute. So For [A,10], [A,5], 5 comes before 10 so it would end up with [A,5],[A,10] for the ordering. The thing you may be missing is that mysortfunction is called multiple times when you use Array.sort until the sorting is completed.
-
dcp about 9 years@user26409021 - A loop is not needed in the mysortfunction function, because Array.sort will call the function as needed until the Array is properly sorted. The only thing mysortfunction is responsible for is determining whether arguments a and b are equal, whether a is less than b, or whether a is greater than b. We don't need a loop to make that determination. Hope that helps.
-
Peter Hansen almost 9 years@GustavoRodrigues maybe because it's too brittle. It would fail to sort in the expected fashion on certain input keys since it just munges the two parts together without a delimiter or other distinction. Consider if var1 and var2 for item X were "foo" and "baz", while var1 for item Y was "foobar". When sorted X should come first but in this case it would be second. This answer could be improved on but as stated it's just not safe.
-
ekkis about 7 yearsas a matter of fact, you could tidy it up with a
.reduce()
-
ekkis about 7 yearshowever,
.localCompare()
returns a -1, 0, 1 so I don't think your solution will work as the || is good for booleans -
Alex Hope O'Connor almost 7 yearswhat is the columns option on the sort data used for?
-
bstst almost 7 years@ekkis, both 1 and -1 are "truthy", so this is a very elegant solution. I've just done this:
sortItems = (a, b) => (a.distance - b.distance) || (a.name - b.name);
and it works like a charm for my non-picky needs. -
Ray Shan over 6 yearsBe careful with performance on large datasets. Every time
thenBy
is called, all the array items are looped through again. -
Teun D over 6 yearsThat is definitely not the case. When you call thenBy(), it constructs a new function that encapsules the previous one. At sorting time, javascript will not strictly "loop through" the items, but it will call the function you pass it many times. The number of calls will not change by using thenBy. For some performance considerations, read: github.com/Teun/thenBy.js#a-word-on-performance
-
Ray Shan over 6 yearsI see, I was incorrect, thanks for thinking about performance. Perhaps add a note about the memory considerations of creating closures with new functions?
-
andi about 6 years@bstst your way is better, because it doesn't evaluate
(a.name - b.name)
unless necessary. Creating variables first does extra work even if it's not needed. -
tbranyen about 6 yearsThat's true, this does more work than necessary, but I would only change it in critical areas. For code that sorts a nominal amount of data, code clarity trumps the perf.
-
Hemil Patel about 5 yearsHow to use this for multiple dynamic then? or in a for loop?
-
Teun D over 4 years@Harry If you can't get it to work, please post an issue with the example that you cannot get to sort, so others can learn too. Happy to help you. github.com/Teun/thenBy.js/issues
-
jtate over 4 yearsWelcome to stackoverflow. In addition to the answer you've provided, please consider providing a brief explanation of why and how this fixes the issue.
-
DanCue almost 4 yearsThis worked great for me! Simple to understand also. Thanks!
-
John Odom over 3 years@dcp Is this expandable to 3+ columns?
-
dcp over 3 years@John Odom - Yes, you would just add the extra sort conditions as needed. For example, if you wanted to sort by name, age, and salary (in that order), you would add the conditions (e.g. if statements) on name first, then on age, then on salary. Order of conditions matters, because the order in which you place the conditions controls the order in which the sorting logic is applied.
-
Guilherme Ferreira over 3 yearsIn my opinion, the correct option. I'll make an improvement, to make work with asc|desc options at the keys. Also one would want to have it as Array.prototype function.
-
Michael Warner over 3 yearsThe String Method is about simplicity and I can't think of a simple way of including asc and desc. So I added a recursion method that allows you to do this. It is a bit more complex but works.
-
Michael Warner over 3 yearsAdding the function to the array prototype is a great idea but that is an implementation detail and reduces the focus of solving the problem when sharing with others.
-
Guilherme Ferreira over 3 yearsMy aproach so far is converting a locale compare for each of the strings on the properties to a 00000000 or 1111111111, join them in the same order, and locale compare again. At the locale compare ill invert the 000>111 based on the asc/desc and the compare result. in the end you'll have a 001101101001001 string to locale compare
-
Michael Warner over 3 yearsThat is a cool approach! The only thing I would say is there is some unnecessary computing as you need to compare all values regardless if you need to or not but in most cases it won’t matter. Awesome man!