119

I have a list of simple scala case class instances and I want to print them in predictable, lexicographical order using list.sorted, but receive "No implicit Ordering defined for ...".

Is there exist an implicit that provides lexicographical ordering for case classes?

Is there simple idiomatic way to mix-in lexicographical ordering into case class?

scala> case class A(tag:String, load:Int)
scala> val l = List(A("words",50),A("article",2),A("lines",7))

scala> l.sorted.foreach(println)
<console>:11: error: No implicit Ordering defined for A.
          l.sorted.foreach(println)
            ^

I am not happy with a 'hack':

scala> l.map(_.toString).sorted.foreach(println)
A(article,2)
A(lines,7)
A(words,50)
Jonik
  • 80,077
  • 70
  • 264
  • 372
ya_pulser
  • 2,620
  • 2
  • 16
  • 21
  • 8
    I've just written up a blog post with a couple of generic solutions [here](http://meta.plasm.us/posts/2013/10/13/ordering-case-classes/). – Travis Brown Oct 13 '13 at 21:04

7 Answers7

173

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.
Hosam Aly
  • 41,555
  • 36
  • 141
  • 182
J Cracknell
  • 3,498
  • 1
  • 19
  • 13
  • 1
    Your example is wonderful! Single-liner and I have default ordering. Thank you very much. – ya_pulser Oct 13 '13 at 19:11
  • You are welcome! I kept my example simple for clarity, but in cases where you sort by many fields, I would perhaps separate key extraction out into a `protected def sortKey`. – J Cracknell Oct 13 '13 at 19:18
  • No need for a separate `sortKey` ... `A.unapply _` is a function of type `A => Option[(String, Int)]`. Mutatis mutandis for every other case class. – Miles Sabin Oct 13 '13 at 22:28
  • Well the observation is that if you are selecting a large composite key, it is cleaner and less error-prone to write `this.sortKey compare that.sortKey` than `(this.a, ..., this.z) compare (...)`. This approch also allows you to define your sorts as code artifacts as seen in om-nom-nom's solution. – J Cracknell Oct 13 '13 at 23:11
  • I think you're missing the point that the standard case class companion `unapply` avoids you having to manually enumerate the case class fields. – Miles Sabin Oct 14 '13 at 11:23
  • Ah, but that is only a solution if you want to sort on every field. :) – J Cracknell Oct 14 '13 at 16:10
  • 7
    The case class A suggested in the answer doesn't seem to compile under scala 2.10. Am I missing something? – Doron Yaacoby Apr 29 '14 at 13:43
  • 3
    @DoronYaacoby: I also get an error `value compare is not a member of (String, Int)`. – bluenote10 Jun 23 '14 at 09:26
  • @DoronYaacoby @bluenote10 I have updated the answer, it looks as though 2.11 may have introduced a compiler bug as it is pretty clear that `scala.math.Ordered` should already be in implicit scope per section 7.2 of the Scala specification. – J Cracknell Jun 25 '14 at 15:26
  • 1
    @JCracknell The error is still there even after the import (Scala 2.10.4). Error occurs during compile, but not get flagged in IDE. (interestingly, it does work properly in REPL). For those who have this problem, the solution at [this SO answer](http://stackoverflow.com/questions/11102393/how-to-lexicographically-compare-scala-tuples/12290441#12290441) works (though not as elegant as the above). If it is a bug, has anyone reported it? – Jus12 Oct 18 '14 at 21:36
  • 2
    FIX: The scope of Ordering is not getting pulled in, you can implicitly pull it in but it's easy enough to just Use Ordering directly: def compare(that: A) = Ordering.Tuple2[String, String].compare(tuple(this), tuple(that)) – brendon Jun 28 '16 at 02:45
  • @brendon what is `tuple()` ? – expert Sep 26 '16 at 21:36
  • I tried case class B(tag: Double, load: Double) extends Ordered[B] { import scala.math.Ordered.orderingToOrdered def compare(that: B): Int = (this.tag, -this.load) compare (that.tag, -that.load) } because I want to reverse the order on second field but it does not work (scala 2.12). I get : too many arguments (2) for method compare: (that: (Double, Double))Int – fricadelle Dec 17 '19 at 10:34
  • It seems I need to add extra parentheses like that (this.tag, -this.load) compare ((that.tag, -that.load)) . Why is that? – fricadelle Dec 17 '19 at 10:49
51
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.

IttayD
  • 28,271
  • 28
  • 124
  • 178
  • 1
    Looks, cool, but I cannot figure out how to use this, I get: `:12: error: not found: value unapply` – zbstof Nov 14 '19 at 12:50
29

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.

Zoltán
  • 21,321
  • 14
  • 93
  • 134
om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
8

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)
  • In case someone is using Scala 3: check the method `Tuple.fromProductTyped`. I've just spent two hours figuring out why the code from this answer doesn't compile in Scala 3. It appears that the `unapply` extractor generated by default is an identity now. – Dawid Łakomy Jun 10 '23 at 21:06
7

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)
Shadowlands
  • 14,994
  • 4
  • 45
  • 43
  • What to do in case of 3+ fields in case class? `l.sortBy( e => e._tag + " " + e._load + " " + ... )` ? – ya_pulser Oct 13 '13 at 12:30
  • If using `sortBy`, then yes, either that, or add/use a suitable function to/on the class (eg `_.toString`, or your own lexographically-significant custom method or external function). – Shadowlands Oct 13 '13 at 12:42
  • Is there any standard function included in to the Scala that can do magic like `List((2,1),(1,2)).sorted` to the case class objects? I see no big difference between named tuples (case class == named tuple) and simple tuples. – ya_pulser Oct 13 '13 at 12:55
  • The nearest I can get along those lines is to use the companion object's unapply method to get an `Option[TupleN]`, then call `get` on that: `l.sortBy(A.unapply(_).get)foreach(println)`, which uses the provided ordering on the corresponding tuple, but this is simply an explicit example of the general idea I give above. – Shadowlands Oct 13 '13 at 13:11
5

Since you used a case class you could extend with Ordered like such:

case class A(tag:String, load:Int) extends Ordered[A] { 
  def compare( a:A ) = tag.compareTo(a.tag) 
}

val ls = List( A("words",50), A("article",2), A("lines",7) )

ls.sorted
Keith Pinson
  • 1,725
  • 1
  • 14
  • 17
0

My personal favorite method is using the SAM(Single abstraction method) with 2.12 as mentioned over the below example:

case class Team(city:String, mascot:String)

//Create two choices to sort by, city and mascot
object MyPredef3 {
  // Below used in 2.11
  implicit val teamsSortedByCity: Ordering[Team] = new Ordering[Team] {
    override def compare(x: Team, y: Team) = x.city compare y.city
  }

  implicit val teamsSortedByMascot: Ordering[Team] = new Ordering[Team] {
    override def compare(x: Team, y: Team) = x.mascot compare y.mascot
  }

  /*
     Below used in 2.12
     implicit val teamsSortedByCity: Ordering[Team] =
    (x: Team, y: Team) => x.city compare y.city
     implicit val teamsSortedByMascot: Ordering[Team] =
    (x: Team, y: Team) => x.mascot compare y.mascot

   */
}

object _6OrderingAList extends App {
  //Create some sports teams
  val teams = List(Team("Cincinnati", "Bengals"),
    Team("Madrid", "Real Madrid"),
    Team("Las Vegas", "Golden Knights"),
    Team("Houston", "Astros"),
    Team("Cleveland", "Cavaliers"),
    Team("Arizona", "Diamondbacks"))

  //import the implicit rule we want, in this case city
  import MyPredef3.teamsSortedByCity

  //min finds the minimum, since we are sorting
  //by city, Arizona wins.
  println(teams.min.city)

}
Sampat Kumar
  • 492
  • 1
  • 6
  • 14