4

I have an array of objects like this:

var booksArray = [  
    {Id:1,Rate:5,Price:200,Name:"History"},
    {Id:2,Rate:5,Price:150,Name:"Geographic"},
    {Id:3,Rate:1,Price:75,Name:"Maths"},
    {Id:4,Rate:2,Price:50,Name:"Statistics"},
    {Id:5,Rate:3,Price:120,Name:"Drawing"},
    {Id:6,Rate:2,Price:100,Name:"Arts"},
    {Id:7,Rate:3,Price:110,Name:"Chemistry"},
    {Id:8,Rate:4,Price:160,Name:"Biology"},
    {Id:9,Rate:4,Price:90,Name:"Software Engineering"},
    {Id:10,Rate:4,Price:80,Name:"Algorithm"},
    {Id:11,Rate:1,Price:85,Name:"Urdu"},
    {Id:12,Rate:1,Price:110,Name:"International Relations"},
    {Id:13,Rate:1,Price:120,Name:"Physics"},
    {Id:14,Rate:3,Price:230,Name:"Electronics"},
    {Id:15,Rate:3,Price:250,Name:"Jihad"},
    {Id:16,Rate:3,Price:200,Name:"Functional Programming"},
    {Id:17,Rate:2,Price:190,Name:"Computer Science"},
    {Id:18,Rate:2,Price:50,Name:"Problem solving Techniques"},
    {Id:19,Rate:6,Price:150,Name:"C#"},
    {Id:20,Rate:7,Price:250,Name:"ASP.Net"}
]

I am sorting it with this function:

function sortBy(key, reverse) {
    var moveSmaller = reverse ? 1 : -1;
    var moveLarger = reverse ? -1 : 1;
    return function(a, b) {
        if (a[key] < b[key]) {
            return moveSmaller;
        }

        if (a[key] > b[key]) {
            return moveLarger;
        }
        return 0;
    };
} 

booksArray.sort(sortBy('Rate', false))
console.log(JSON.stringify(booksArray))

And this is producing this result:

[
    {Id:3,Rate:1,Price:75,Name:"Maths"},
    {Id:11,Rate:1,Price:85,Name:"Urdu"},
    {Id:12,Rate:1,Price:110,Name:"International Relations"},
    {Id:13,Rate:1,Price:120,Name:"Physics"},
    {Id:4,Rate:2,Price:50,Name:"Statistics"},
    {Id:6,Rate:2,Price:100,Name:"Arts"},
    {Id:17,Rate:2,Price:190,Name:"Computer Science"},
    {Id:18,Rate:2,Price:50,Name:"Problem solving Techniques"},
    {Id:5,Rate:3,Price:120,Name:"Drawing"},
    {Id:7,Rate:3,Price:110,Name:"Chemistry"},
    {Id:14,Rate:3,Price:230,Name:"Electronics"},
    {Id:15,Rate:3,Price:250,Name:"Jihad"},
    {Id:16,Rate:3,Price:200,Name:"Functional Programming"},
    {Id:8,Rate:4,Price:160,Name:"Biology"},
    {Id:9,Rate:4,Price:90,Name:"Software Engineering"},
    {Id:10,Rate:4,Price:80,Name:"Algorithm"},
    {Id:1,Rate:5,Price:200,Name:"History"},
    {Id:2,Rate:5,Price:150,Name:"Geographic"},
    {Id:19,Rate:6,Price:150,Name:"C#"},
    {Id:20,Rate:7,Price:250,Name:"ASP.Net"}
]

You can see it is sorting on the Rate field which is fine. Now I want to resort again the parts of array without disturbing Rate sorting. I need output like this:

[
    {Id:12,Rate:1,Price:110,Name:"International Relations"},
    {Id:3,Rate:1,Price:75,Name:"Maths"},
    {Id:13,Rate:1,Price:120,Name:"Physics"},
    {Id:11,Rate:1,Price:85,Name:"Urdu"},
    {Id:6,Rate:2,Price:100,Name:"Arts"},
    {Id:17,Rate:2,Price:190,Name:"Computer Science"},
    {Id:18,Rate:2,Price:50,Name:"Problem solving Techniques"},
    {Id:4,Rate:2,Price:50,Name:"Statistics"},
    {Id:7,Rate:3,Price:110,Name:"Chemistry"},
    {Id:5,Rate:3,Price:120,Name:"Drawing"},
    {Id:14,Rate:3,Price:230,Name:"Electronics"},
    {Id:16,Rate:3,Price:200,Name:"Functional Programming"},
    {Id:15,Rate:3,Price:250,Name:"Jihad"},
    {Id:10,Rate:4,Price:80,Name:"Algorithm"},
    {Id:8,Rate:4,Price:160,Name:"Biology"},
    {Id:9,Rate:4,Price:90,Name:"Software Engineering"},
    {Id:2,Rate:5,Price:150,Name:"Geographic"},
    {Id:1,Rate:5,Price:200,Name:"History"},
    {Id:19,Rate:6,Price:150,Name:"C#"},
    {Id:20,Rate:7,Price:250,Name:"ASP.Net"}
]

Here you can see it is sorted on Rate as well as Name.

Why am I not doing sorting with multiple keys once and for all? Because I am creating a function which sorts the array of objects and can be called multiple times.

Example:

myarray.order('Rate') // single sort
myarray.order('Rate').order('Name') // multiple sort

I can use some more parameters in my function to track if array has already been sorted.

Sample Fiddle

Muhammad Raheel
  • 19,823
  • 7
  • 67
  • 103
  • Do you want to sort on `Rate` as a priority, and then on `Name`? Like `ORDER BY Rate, Name` in SQL? – halfer Aug 06 '14 at 11:35
  • 1
    The sorts happen independently, and have no idea that a preceding sort needs to be preserved. [Is this](https://stackoverflow.com/questions/2784230/javascript-how-do-you-sort-an-array-on-multiple-columns) any good? I think it might be a duplicate. – halfer Aug 06 '14 at 11:44
  • Maybe have a look at [Fast stable sorting algorithm implementation in javascript](http://stackoverflow.com/questions/1427608/fast-stable-sorting-algorithm-implementation-in-javascript) – Bergi Aug 06 '14 at 13:57

3 Answers3

2

Assuming that you already have bookArray sorted by Rate. It can be sorted on second level using following. Can be fine tuned better for easy usability.

var arr = bookArray;
var firstSortKey = 'Rate';
var secondSortKey = 'Name';
var count = 0 ;
var firstSortValue = arr[count][firstSortKey];

for(var i=count+1; i<arr.length; i++){
     if(arr[i][firstSortKey]!==firstSortValue){
         var data = arr.slice(count, i);
         data = data.sort(function(a, b){
             return a[secondSortKey]>b[secondSortKey];
         });
         var argsArray = [count, i-count];
         argsArray.push.apply(argsArray, data);
         Array.prototype.splice.apply(arr, argsArray);
         count = i;
         firstSortValue =  arr[i][firstSortKey];
     }
}

Fiddle Demo

Muhammad Raheel
  • 19,823
  • 7
  • 67
  • 103
Vishwanath
  • 6,284
  • 4
  • 38
  • 57
0

Just for fun: here's a generic sorter for multiple levels of field values within an Array of Objects.

Syntax

XSort().create([arrOfObjects])
  .orderBy(
        {key: [keyname], [descending=0/1] }, 
        {key: [keyname], [descending=0/1]},
        ... );

See jsFiddle for an example using booksArray.

function XSort() {
    const multiSorter = sortKeys => {
      if (!sortKeys || sortKeys[0].constructor !== Object) {
        throw new TypeError("Provide at least one {key: [keyname]} to sort on");
      }

      return function (val0, val1) {
        for (let sortKey of sortKeys) {
          const v0 = sortKey.key instanceof Function 
            ? sortKey.key(val0) : val0[sortKey.key];
          const v1 = sortKey.key instanceof Function 
            ? sortKey.key(val1) : val1[sortKey.key];
          const isString = v0.constructor === String || v1.constructor === String;
          const compare = sortKey.descending ?
            isString ? v1.toLowerCase().localeCompare(v0.toLowerCase()) : v1 - v0 :
            isString ? v0.toLowerCase().localeCompare(v1.toLowerCase()) : v0 - v1;
          if ( compare !== 0 ) { return compare; }
        }
      };
    }
    const Sorter = function (array) {
      this.array = array;
    };

    Sorter.prototype = {
      orderBy: function(...sortOns) {
        return this.array.slice().sort(multiSorter(sortOns));
      },
    };

    return {
      create: array => new Sorter(array)
    };
}
KooiInc
  • 119,216
  • 31
  • 141
  • 177
0

Change your sortBy function to accept an array of keys. Something like:

function sortBy(keys, reverse) {
    var moveSmaller = reverse ? 1 : -1;
    var moveLarger = reverse ? -1 : 1;
    var key = keys.shift();

    return function(a, b) {
        if (a[key] < b[key]) {
            return moveSmaller;
        }

        if (a[key] > b[key]) {
            return moveLarger;
        }
        return keys.length ? (sortBy(keys, reverse))(a, b) : 0;
    };
} 

booksArray.sort(sortBy(['Rate', 'Name'], false))

(Untested, jsfiddle is down for me)

RoToRa
  • 37,635
  • 12
  • 69
  • 105