Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Easy idiomatic way to define Ordering for a simple case class

People also ask

What is implicit ordering?

Implicit means the order of items (e.g. codes) as returned within the array of codes. There are thus always naturally given in a structure definition. Explicit order is the order provided through annotations and is optional. Explicit order prevails implicit order.

What are case class?

A case class has all of the functionality of a regular class, and more. When the compiler sees the case keyword in front of a class , it generates code for you, with the following benefits: Case class constructor parameters are public val fields by default, so accessor methods are generated for each parameter.

What is a case class in Scala?

Scala case classes are just regular classes which are immutable by default and decomposable through pattern matching. It uses equal method to compare instance structurally. It does not use new keyword to instantiate object. All the parameters listed in the case class are public and immutable by default. Syntax.

How do I sort a list in Scala?

Use the sortWith() Function to Sort List in Scala. We used the sortWith() function to take a lambda expression as an argument and return the sorted result. We can use this function to sort the list in ascending and descending order.


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 Orderings for Tuples 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.

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.


To summarize, there are three ways to do this:

  1. For one-off sorting use .sortBy method, as @Shadowlands have showed
  2. For reusing of sorting extend case class with Ordered trait, as @Keith said.
  3. 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.


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)

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)