Groovy: Sort list of objects on the basis of more than one field

25 / Jan / 2011 by Salil 5 comments

Usually, when we deal with databases, we don’t face such kind of situation because we query database to get result-set in required order.

But let’s say you have a List of objects that you want to sort on the basis of two fields. Let’s discuss it with an example.

I have a list of Tasks (with date and priority fields). I want to sort these tasks ordered by Date and priority-wise (as second level sorting).

Here, I am going to use Expando class, so I can directly run this in my groovyConsole. But definitely, you can use some ‘Task’ class.

Expando a = new Expando(date: Date.parse('yyyy-MM-dd','2011-01-01'), priority:1)
Expando b = new Expando(date: Date.parse('yyyy-MM-dd','2011-01-01'), priority:2)
Expando c = new Expando(date: Date.parse('yyyy-MM-dd','2011-01-02'), priority:1)
Expando d = new Expando(date: Date.parse('yyyy-MM-dd','2011-01-01'), priority:3)

def list = [a,d,c,b]

If sorting was required on the basis of date only, it was very simple.

list.sort(it.date)

But as per our requirements – order by date (first level sorting) and priority (second level sorting). We can do something like following.

list.sort{x,y->
  if(x.date == y.date){
    x.priority <=> y.priority
  }else{
    x.date <=> y.date
  }
}

Well, there could be some better way. If you know please put your comments. But it worked well in my case.

Contact our groovy developers in case you have any query

Cheers!

FOUND THIS USEFUL? SHARE IT

comments (5)

  1. Salil

    awesome man! Thanks Ted and Oleg. I liked it with both the ways.

    Oleg for your solution – order needs to be put like —

    list.sort{[it.priority, it.date]}
    

    Seems secondary sorting-order needs to come first.

    Now my next step was to checkout which one is better?
    I am no-one to claim this. But I ran a test (with 10000 objects) on my groovyConsole – which says: —

    list.sort{x,y -> x.date<=>y.date ?: x.priority<=>y.priority}
    

    is slightly faster than other one.

    outputs for running with comparator (10 times):
    [140,177,114,146,117,134,125,139,121,116] — in milliseconds.

    And for another way list.sort{[it.priority, it.date]}:
    [280,212,208,218,185,243,191,159,185,220,219] — in milliseconds.

    here is that test-script, you can verify if it’s correct or not..

    def list = []
    Date now = new Date()
    Random r = new Random()
    (1..10000).each{i->
     list << new Expando(date: now+r.nextInt(10), priority:r.nextInt(10))  
    }
    
    Long start = System.currentTimeMillis()
    
    list.sort{x,y -> x.date<=>y.date ?: x.priority<=>y.priority}
    // list.sort{[it.priority, it.date]}   // another-way
    Long end = System.currentTimeMillis()
    
    println "time taken: ${end - start}"
    

    I think this is further blogable :-)

    Reply
  2. Ted Naleid

    shoot, the comment form ate a bunch of my stuff, even though I put it in a pre, replace S-S with the spaceship sort comparison operator.

    x.date S=S y.date ?: x.priority S=S y.priority

    Reply
  3. Ted Naleid

    You can do it with one less comparison operation using the elvis operator to check priority if date is false/0 (meaning they are equal), otherwise it’ll short circuit and return the -1/1 from the date comparison:

    list.sort { x, y ->
    x.date y.date ?: x.priority y.priority
    }

    Reply

Leave a comment -