Sunday, October 26, 2008

Sorting in Groovy vs. Java

When you switch from Java to Groovy, one of the thing things you notice right away is how Closures affect things like sorting. As an example, lets pretend we have Things

public class Thing {
public int i1;
public String val;
public Thing(String s, int i) {
val = s;
i1 = i;
}
// for easy list printing
public String toString() {
return val +"-"+i1;
}
}

(Lets ignore the public member and all – this is just to make the code shorter)

Now lets make a list of Things in Java

List<Thing> l = new ArrayList<Thing>();
l.add(new Thing("hello", 3));
l.add(new Thing("there", 1));
l.add(new Thing("apple", 5));
l.add(new Thing("orange", 10));

and in Groovy

l = [new Thing("hello", 3),
new Thing("there", 1),
new Thing("apple", 5),
new Thing("orange", 10)];

Of course, the default way to sort in Java is fairly straightforward, though of course it doesn’t work here:

Collections.sort(l);

This won’t even compile since our Thing doesn’t implement Comparable. Groovy actually executes this no problem (though the order it sorts it in is anyone’s guess.) Running this snippet works:

println()
l.sort()
println “After Sort:”
println(l)

and prints the following result:

[hello-3, there-1, apple-5, orange-10]
After sort:
[apple-5, orange-10, hello-3, there-1]

But as you can see there is no way of knowing the sort order. So of course that is where implementing Comparable kicks in. I won’t go into that since that is basic Java and if we defined Thing as Comparable it would work as expected in Groovy as well.
Java, of course, also allows us to pass a Comparator to the sort function and this will work without changing Thing:

System.out.println(l);
Collections.sort(l, new Comparator<Thing> () {
public int compare(Thing o1, Thing o2) {
return o1.i1 - o2.i1; // sort by the int value
}
});
System.out.println("After Sorting:");
System.out.println(l);

Which will yield:

[hello-3, there-1, apple-5, orange-10]
After Sorting:
[there-1, hello-3, apple-5, orange-10]
Groovy has a similar option:

println(l)
l.sort([compare:{a,b-> Math.abs(a.i1) - Math.abs(b.i1) } ] as Comparator)
println "After sort:"
println(l)

Note the syntax for defining the Comparator on the fly in line 2. We are creating a map with functions and then converting it to a Comparator object. Very cool and powerful on the one hand but on the other kind of wacky for someone just out of Java. (Perhaps the topic for another post)You can also do this via a Closure syntax. (This is the way you would do this for these examples – the Comparator object is better for cases where you need to define a Comparator and pass it around). Here is the Closure way:

l.sort { o1, o2 ->
o1.i1 - o2.i1; // sort by the int value
}

This has been sorting with passing in a Comparator way. For that, Groovy just offers alternative (better?) syntax over Java. Groovy, however, does offer another way to use the sort function. There is version of sort that takes a much simplified Closure that won’t require us to remember exactly how to use Java’s Comparator – i.e. a positive or negative value for which way to sort.

The GDK Docs describe the function as:

sort
public List sort(Closure closure)

Sorts this Collection using the given closure as a comparator. The closure is passed each item from the collection, and is assumed to return a comparable value (i.e. an int).
Parameters:
closure - a Closure used as a comparator.
Returns:
a newly created sorted List

So, you pass a Closure to sort that turns the Object you want into a Comparable object. In our example if we want to sort our Things based on their int values you can just say:

l.sort { it.i1 }

Or for the String , just return it:

l.sort { i.val }
This works great to sort the list in the ascending order. Using this to sort the list in reverse order takes some maneuvering. For int this is still fairly straightforward.

l.sort { -1 * it.i1 }

I couldn’t get a good similar trick for Strings though – so you may need to use the Comparator or just call reverse on the returned list:

l.sort{ it.val }.reverse()

As you can see the Closure thing, can really shorten up the syntax and gives more power to what you can do in less code.

One downside of the whole Closure thing is that you can’t always tell what type of Closure a function expects to receive. If you use an editor with command completion you can only see that a function wants a Closure

image
and you have no way of knowing how many parameters will be passed to the Closure and what the function expects the Closure to return. In this case, the Javadocs explained what is needed but that is more difficult to look at or find sometimes rather than just being obvious from a method signature. Part of this is a function of a dynamic languages and not just the Closure issue.

Another technical question is that I am also not sure how Groovy distinguishes between the Closure version of the Comparator and the straight Closure which returns the Comparable object. My current guess is that there is code in the Closure version to check the number of parameters the Closure passed in expects to receive and calls it accordingly – because straight method dispatching should send them both to the same function. I will have to check into that.

8 comments:

Rick Reumann said...

Thanks for this post. It helped a lot. I didn't even think of using reverse() for the String list... cool.

yt said...

Glad it helped.
The reverse thing was a last second Aha I had while writing the post.

Michael Finney said...

Nice post. This is what I was looking for. :)

Sudhakar said...

How do I sort : where I have one-many relationship?
Person and Address are domain objects. Person has many addresses. I have list of persons with addresses. I want to sort on city column on Address table. How do I do this? Pls help me.

Sudhakar said...

I need list of persons with addresses (sort on city). any way to get?

Anonymous said...

You can also use a two-arg closure such as

lines.sort { String a, String b -> -1 * a.compareTo(b) }

rather than reverse() on the result.

Anonymous said...

Very helpful post, thank-you!!

Anonymous said...

stumbled upon this..was very helpful and informative, thanks!