Eldon Lesley
Eldon Lesley

Reputation: 935

JavaScript Sort Array of Objects by Multipe Fields in Chrome

From what it said, it sounds very simple, but I wasn't succeeded to achieve the perfect result in Chrome (I did it in IE)

So I have an object:

var objs=[
{ name: "water", type: "cold" },
{ name: "tea", type: "hot" },
{ name: "coffee", type: "hot" },
{ name: "choco", type: "cold" }
];

I sorted it by name, ascending and I got the expected result:

  1. choco,cold
  2. coffee, hot
  3. tea, hot
  4. water, cold

I got the same result both in IE and Chrome,

Then, I sort it again by type, ascending, and my expectation:

  1. choco, cold
  2. water, cold
  3. coffee, hot
  4. tea, hot

But I only got the expected result in IE, while in Chrome the result was almost the same except that "water" came at the top (it ruins the first sorting)

any general approach idea? (I need to keep it one function call per field, not sorting by multiple fields in one function at the same time)

My sort method was like this:

function SortBy(key,Asc,method){
  var GetKey=function(x) {return method ? method(x[key]) : x[key] };
  return function(a,b){
    var A=GetKey(a), B=GetKey(b);
    return ((A<B) ? -1 : ((A>B) ? 1 : 0)) * [-1,1][+!!Asc];
  }
}

var sortedObjects=Clone(objs); //clone the current objs

sortedObjects.sort(SortBy("name" , true, function(x){
  if (typeof(x)=="string") return x.toLowerCase(); else return x;
}));

objs=sortedObjects; //got the first result

sortedObjects.sort(SortBy("type",true,function(x){
  if (typeof(x)=="string") return x.toLowerCase(); else return x;
}));

objs=sortedObjects;

EDITED here is the fiddle (the problem is the "Water"): DEMO

Upvotes: 0

Views: 117

Answers (2)

Paul S.
Paul S.

Reputation: 66364

If we read the ES5 spec §15.4.4.11 Array.prototype.sort, the very second sentence states

The sort is not necessarily stable (that is, elements that compare equal do not necessarily remain in their original order)

That is to say, in JavaScript, if the comparator says two things are equal (by giving 0), it doesn't matter if they get swapped or not and hence in some environments they may swapped due to some other reason such as their hashes or just the implementation.

i.e. The result you experience is a valid result of sort as defined by the spec and repeating the same sort may even give different results each time


Hopefully I won't get downvoted too heavily for this, but if you do it as one function then you don't need to iterate through the array as many times and you'll avoid your unexpected result

/*  list = multiSort(list [, obj, ...])
 *  where e.g. obj = {
 *      key: "keyToLookAt",
 *      optional comparator: function (a, b) {return a<b || ((a===b)-1);},
 *      optional ascending: default true
 *  }
*/

var multiSort = (function () {
    function generateSorter(key, comparator, ascending) {
        ascending = 2 * !!ascending - 1;
        if (comparator) {
            return function sorter(a, b) {
                return ascending * comparator(a[key], b[key]);
            };
        }
        return function sorter(a, b) {
            a = a[key];
            b = b[key];
            return ascending * (a < b ? -1 : a > b ? 1 : 0);
        };
    }
    function sortManager(sorters) {
        return function (a, b) {
            var ret, i = 0;
            for (; i < sorters.length; ++i) {
                ret = sorters[i](a, b);
                if (ret !== 0) return ret;
            }
            return 0;
        };
    }
    function multiSort(list) {
        var i = 1, sorters = [];
        for (; i < arguments.length; ++i) {
            sorters.push(generateSorter(
                arguments[i].key,
                arguments[i].comparator,
                'ascending' in arguments[i] ? arguments[i].ascending : 1
            ));
        }
        list.sort(sortManager(sorters));
        return list;
    }
    return multiSort;
}());

And then when using it, the "most strict" sort rule goes first

multiSort(
    [
        { name: "water", type: "cold" },
        { name: "tea", type: "hot" },
        { name: "coffee", type: "hot" },
        { name: "choco", type: "cold" }
    ],
    {key: 'type'},
    {key: 'name'}
);
/*
[
    { "name": "choco" , "type": "cold" },
    { "name": "water" , "type": "cold" },
    { "name": "coffee", "type": "hot" },
    { "name": "tea"   , "type": "hot" }
]
*/

Upvotes: 1

Luan Castro
Luan Castro

Reputation: 1184

I created a CodePen to test and work fine... I write the same idea in more readable code

function SortBy(key, isAsc, fn){
     fn = fn || function (x){ return x; };

    return function(objA, objB){
       var a = fn(objA[key]);
       var b = fn(objB[key]);

       var isGreater = isAsc ? 1 : -1;
       var isLesser = -isGreater;
       var isSame = 0;

       if( a > b ) return isGreater;
       if( a == b ) return isSame;
       if( a < b ) return isLesser;
       return -1; //to down the undefined

     }  

}

http://codepen.io/anon/pen/sGgDq edited and tested with a lot of data

p.s.: I used codepen because jsfiddle not open in my work

Upvotes: 0

Related Questions