Easy idiomatic way to define Ordering for a simple case class
Solution 1:
My personal favorite method is to make use of the provided implicit ordering for Tuples, as it is clear, concise, and correct:
case class A(tag: String, load: Int) extends Ordered[A] {
// Required as of Scala 2.11 for reasons unknown - the companion to Ordered
// should already be in implicit scope
import scala.math.Ordered.orderingToOrdered
def compare(that: A): Int = (this.tag, this.load) compare (that.tag, that.load)
}
This works because the companion to Ordered
defines an implicit conversion from Ordering[T]
to Ordered[T]
which is in scope for any class implementing Ordered
. The existence of implicit Ordering
s for Tuple
s enables a conversion from TupleN[...]
to Ordered[TupleN[...]]
provided an implicit Ordering[TN]
exists for all elements T1, ..., TN
of the tuple, which should always be the case because it makes no sense to sort on a data type with no Ordering
.
The implicit ordering for Tuples is your go-to for any sorting scenario involving a composite sort key:
as.sortBy(a => (a.tag, a.load))
As this answer has proven popular I would like to expand on it, noting that a solution resembling the following could under some circumstances be considered enterprise-grade™:
case class Employee(id: Int, firstName: String, lastName: String)
object Employee {
// Note that because `Ordering[A]` is not contravariant, the declaration
// must be type-parametrized in the event that you want the implicit
// ordering to apply to subclasses of `Employee`.
implicit def orderingByName[A <: Employee]: Ordering[A] =
Ordering.by(e => (e.lastName, e.firstName))
val orderingById: Ordering[Employee] = Ordering.by(e => e.id)
}
Given es: SeqLike[Employee]
, es.sorted()
will sort by name, and es.sorted(Employee.orderingById)
will sort by id. This has a few benefits:
- The sorts are defined in a single location as visible code artifacts. This is useful if you have complex sorts on many fields.
- Most sorting functionality implemented in the scala library operates using instances of
Ordering
, so providing an ordering directly eliminates an implicit conversion in most cases.
Solution 2:
object A {
implicit val ord = Ordering.by(unapply)
}
This has the benefit that it is updated automatically whenever A changes. But, A's fields need to be placed in the order by which the ordering will use them.
Solution 3:
To summarize, there are three ways to do this:
- For one-off sorting use .sortBy method, as @Shadowlands have showed
- For reusing of sorting extend case class with Ordered trait, as @Keith said.
-
Define a custom ordering. The benefit of this solution is that you can reuse orderings and have multiple ways to sort instances of the same class:
case class A(tag:String, load:Int) object A { val lexicographicalOrdering = Ordering.by { foo: A => foo.tag } val loadOrdering = Ordering.by { foo: A => foo.load } } implicit val ord = A.lexicographicalOrdering val l = List(A("words",1), A("article",2), A("lines",3)).sorted // List(A(article,2), A(lines,3), A(words,1)) // now in some other scope implicit val ord = A.loadOrdering val l = List(A("words",1), A("article",2), A("lines",3)).sorted // List(A(words,1), A(article,2), A(lines,3))
Answering your question Is there any standard function included into the Scala that can do magic like List((2,1),(1,2)).sorted
There is a set of predefined orderings, e.g. for String, tuples up to 9 arity and so on.
No such thing exists for case classes, since it is not easy thing to roll off, given that field names are not known a-priori (at least without macros magic) and you can't access case class fields in a way other than by name/using product iterator.
Solution 4:
The unapply
method of the companion object provides a conversion from your case class to an Option[Tuple]
, where the Tuple
is the tuple corresponding to the first argument list of the case class. In other words:
case class Person(name : String, age : Int, email : String)
def sortPeople(people : List[Person]) =
people.sortBy(Person.unapply)
Solution 5:
The sortBy method would be one typical way of doing this, eg (sort on tag
field):
scala> l.sortBy(_.tag)foreach(println)
A(article,2)
A(lines,7)
A(words,50)