Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting an Array of Objects by two Properties

I've got an Array of Objects I want to sort by two Properties:

  1. RemindingTimestamp
  2. ModificationTimestamp

Sorting order: desc

Sorting this Object by one Property isn't the problem, but in this case I've no idea how to get it work.

like image 550
Sven Kannenberg Avatar asked Apr 14 '12 13:04

Sven Kannenberg


People also ask

How do you sort an array by two conditions?

The sort() callback function usually receives two arguments, say a and b, which are nothing but two elements of the array on which sort() was called and the callback function runs for each possible pair of elements of the array.


3 Answers

Presuming the timestamps themselves sort ok (e.g. ISO8601 and same time zone), try:

myArray.sort(function(a,b) {
  var x = a.RemindingTimestamp - b.RemindingTimestamp;
  return x == 0? a.ModificationTimestamp - b.ModificationTimestamp : x;
}

Edit - response to comments

A descending sort is achieved by changing the order of subtraction, or multiplying the result by -1. Dates that don't sort because they don't subtract (e.g. 2012-04-12) can be handled by conversion to dates first, e.g.

// Convert ISO8601 date string to date object
// Assuming date is ISO8601 long format, ignores timezone
function toDate(s) {
  var bits = s.split(/[-T :]/);
  var d = new Date(bits[0], bits[1]-1, bits[2]);
  d.setHours(bits[3], bits[4], parseFloat(bits[5])); 
  return d;
}

// Source data, should end up sorted per n
var myArray = [ 
  {RemindingTimestamp: '2012-04-15T23:15:12Z', 
   ModificationTimestamp: '2012-04-15T23:15:12Z', n: 4},
  {RemindingTimestamp: '2012-04-12T23:15:12Z', 
   ModificationTimestamp: '2012-04-12T23:15:12Z', n: 1},
  {RemindingTimestamp: '2012-04-12T23:15:12Z', 
   ModificationTimestamp: '2012-04-13T23:15:12Z', n: 2},
  {RemindingTimestamp: '2012-04-12T23:15:12Z', 
   ModificationTimestamp: '2012-04-13T23:15:14Z', n: 3}
];

// Sort it
myArray.sort(function(a,b) {
  var x = toDate(a.RemindingTimestamp) - toDate(b.RemindingTimestamp);
  return x? x : toDate(a.ModificationTimestamp) - toDate(b.ModificationTimestamp);
});

// Just to show the result
function sa(o) {
  var result = [], t;
  for (var i=0; i<o.length; i++) {
    t = o[i]; 
      result.push(t.n);
  }
  alert(result);
}

sa(myArray); // 1,2,3,4

The conversion of date string to date object can be extended to handle time zone if required (for ISO8601 compliant strings only, those that use time zone abbreviations instead of the actual offset are unreliable).

like image 58
RobG Avatar answered Oct 18 '22 16:10

RobG


function compareObject(obj1, obj2){
    if(obj1.RemindingTimestamp > obj2.RemindingTimestamp)
        return - 1;
    if(obj2.RemindingTimestamp > obj1.RemindingTimestamp)
        return 1;

    // obj1.RemindingTimestamp == obj2.RemindingTimestamp

    if(obj1.ModificationTimestamp > obj2.ModificationTimestamp)
        return -1;
    if(obj2.ModificationTimestamp > obj1.ModificationTimestamp)
        return 1;

    return 0;
}

myObjects.sort(compareObject);

JSFiddle Demo

Resources:

  • MDN: Array.sort()
like image 34
Zeta Avatar answered Oct 18 '22 15:10

Zeta


Custom comparators take the form:

myArray.sort(function(a,b){
  var m1=a1.RemindingTimestamp,
      m2=a2.RemindingTimestamp,
      n1=a1.ModificationTimestamp,
      n2=a2.ModificationTimestamp;
  return m1<m2 ? -1 : m1>m2 ? 1 :
         n1<n2 ? -1 : n1>n2 ? 1 : 0;
});

For descending sort, swap the < and > (or swap 1 and -1).

While you can make your own custom comparator each time you need this, I have created a method designed explicitly for easily sorting by multiple criteria, using a Schwartzian transform (which may be faster but more memory hungry in some circumstances): http://phrogz.net/js/Array.prototype.sortBy.js

In short:

myArray.sortBy(function(obj){
  return [obj.RemindingTimestamp, obj.ModificationTimestamp];
}).reverse();

The reverse is there since you mentioned that you wanted a descending sort. If both RemindingTimestamp and ModificationTimestamp are numbers, you could alternatively do:

myArray.sortBy(function(obj){
  return [-obj.RemindingTimestamp, -obj.ModificationTimestamp];
});

Here is the code that adds sortBy to arrays:

(function(){
  // Extend Arrays in a safe, non-enumerable way
  if (typeof Object.defineProperty === 'function'){
    // Guard against IE8's broken defineProperty
    try{Object.defineProperty(Array.prototype,'sortBy',{value:sb}); }catch(e){}
  }
  // Fall back to an enumerable implementation
  if (!Array.prototype.sortBy) Array.prototype.sortBy = sb;

  function sb(f){
    for (var i=this.length;i;){
      var o = this[--i];
      this[i] = [].concat(f.call(o,o,i),o);
    }
    this.sort(function(a,b){
      for (var i=0,len=a.length;i<len;++i){
        if (a[i]!=b[i]) return a[i]<b[i]?-1:1;
      }
      return 0;
    });
    for (var i=this.length;i;){
      this[--i]=this[i][this[i].length-1];
    }
    return this;
  }
})();

Here are some more examples from the docs:

var a=[ {c:"GK",age:37}, {c:"ZK",age:13}, {c:"TK",age:14}, {c:"AK",age:13} ];

a.sortBy( function(){ return this.age } );                                  
  --> [ {c:"ZK",age:13}, {c:"AK",age:13}, {c:"TK",age:14}, {c:"GK",age:37} ]

a.sortBy( function(){ return [this.age,this.c] } );                         
  --> [ {c:"AK",age:13}, {c:"ZK",age:13}, {c:"TK",age:14}, {c:"GK",age:37} ]

a.sortBy( function(){ return -this.age } );                                 
  --> [ {c:"GK",age:37}, {c:"TK",age:14}, {c:"ZK",age:13}, {c:"AK",age:13} ]


var n=[ 1, 99, 15, "2", "100", 3, 34, "foo", "bar" ];

n.sort();
  --> [ 1, "100", 15, "2", 3, 34, 99, "bar", "foo" ]

n.sortBy( function(){ return this*1 } );
  --> [ "foo", "bar", 1, "2", 3, 15, 34, 99, "100" ]

n.sortBy( function(o){ return [typeof o,this] } );
  --> [1, 3, 15, 34, 99, "100", "2", "bar", "foo"]

n.sortBy(function(o){ return [typeof o, typeof o=="string" ? o.length : o] })
  --> [1, 3, 15, 34, 99, "2", "100", "bar", "foo"]

Note in the last example that (typeof this) happens not to be the same as (typeof o); see this post for more details.

like image 26
Phrogz Avatar answered Oct 18 '22 16:10

Phrogz