How do you sort an array on multiple columns?

166,642

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.

An example can be seen here.

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)

here is a jsbin example.

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);
Share:
166,642
flavour404
Author by

flavour404

Updated on January 16, 2022

Comments

  • flavour404
    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 by publication_name. I know in JavaScript you have Array.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, then publication_name?

  • defau1t
    defau1t almost 11 years
    May I know from where did you collect this data [[ "Nilesh","Karmshil"], ["Pranjal","Deka"], ["Susants","Ghosh"], ["Shiv","Shankar"], ["Javid","Ghosh"], ["Shaher","Banu"], ["Javid","Rashid"]];
  • Bla...
    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
    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
    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
    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
    ekkis about 7 years
    as a matter of fact, you could tidy it up with a .reduce()
  • ekkis
    ekkis about 7 years
    however, .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
    Alex Hope O'Connor almost 7 years
    what is the columns option on the sort data used for?
  • bstst
    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
    Ray Shan over 6 years
    Be careful with performance on large datasets. Every time thenBy is called, all the array items are looped through again.
  • Teun D
    Teun D over 6 years
    That 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
    Ray Shan over 6 years
    I see, I was incorrect, thanks for thinking about performance. Perhaps add a note about the memory considerations of creating closures with new functions?
  • andi
    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
    tbranyen about 6 years
    That'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
    Hemil Patel about 5 years
    How to use this for multiple dynamic then? or in a for loop?
  • Teun D
    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
    jtate over 4 years
    Welcome 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
    DanCue almost 4 years
    This worked great for me! Simple to understand also. Thanks!
  • John Odom
    John Odom over 3 years
    @dcp Is this expandable to 3+ columns?
  • dcp
    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
    Guilherme Ferreira over 3 years
    In 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
    Michael Warner over 3 years
    The 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
    Michael Warner over 3 years
    Adding 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
    Guilherme Ferreira over 3 years
    My 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
    Michael Warner over 3 years
    That 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!