Search code examples
sortinggroovy

Groovy "spaceship operator" when sorting lists-of-lists


If I have a list-of-lists, then .sort() works naturally; i.e., element-wise:

groovy:000> [[3,2,1],[3,1,2],[2,1,3],[2,3,1],[1,2,3],[1,3,2],[1,3,3],[1,2,2]].sort()                      
===> [[1, 2, 2], [1, 2, 3], [1, 3, 2], [1, 3, 3], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

However, if I write an explicit sort closure, using the <=> operator, it fails:

groovy:000> [[3,2,1],[3,1,2],[2,1,3],[2,3,1],[1,2,3],[1,3,2],[1,3,3],[1,2,2]].sort { a, b -> a <=> b }
ERROR java.lang.IllegalArgumentException:
Cannot compare java.util.ArrayList with value '[3, 1, 2]' and java.util.ArrayList with value '[3, 2, 1]'
        at groovysh_evaluate$_run_closure1.doCall (groovysh_evaluate:3)

In this toy case, it doesn't matter, but I have a more complicated structure that requires me to use a sort closure. I understand that <=>, per the documentation, is syntactic sugar for .compareTo, which java.util.ArrayList values don't have. However, how does the "bare sort" get around this problem?

The reason I ask is because I found a solution, but it feels a bit cludgy:

listOfLists.sort { a, b -> new Tuple(*a) <=> new Tuple(*b) }

I wonder if there's a more idiomatic solution that effectively does the same as the bare sort?


Solution

  • <=> is an overloaded operator for Comparable.compareTo; therefor the LHS must implement this method and List does not, hence the error.

    When using just [].sort(), then groovy uses NumberAwareComparator, which tries to use DefaultTypeTransformation.compareTo, which again expects the LHS to be a Comparable (or a Number, but we can ignore this here). Since the LHS is not a Comparable, this method throws and NumberAwareComparator falls back to comparing the hash codes of both sides.

    Whether to trust this behaviour is a different story. Wrapping the comparison in a container, that can compareTo (like Tuple) feels way safer (e.g. it properly short-circuits)