Todd O'Bryan
Todd O'Bryan

Reputation: 2260

Scala Ordered by multiple fields

This question is probably quite dumb, but I can't find an example and can't figure it out.

I want to compare two Person classes by last, first, and middle name, in that order. Here's the brain-dead way to do it:

def compare(that: Person): Int = {
  val last: Int = lastName.compare(that.lastName)
  if (last != 0) last
  else {
    val first: Int = firstName.compare(that.firstName)
    if (first != 0) first
    else middleName.compare(that.middleName)
}

I know there's some much more clever way to do this (probably using Ordering) but I can't put my finger on it.

Todd

I figured out this once I realized how to access the right things in Ordering.

def compare(that: Person): Int = {
  Ordering.Tuple3(Ordering.String, Ordering.String, Ordering.String).compare(
     (lastName, firstName, middleName),
     (that.lastName, that.firstName, that.middleName))
}

I'm pretty sure I can get away with fewer explicits, but this works and is reasonably compact.

Upvotes: 13

Views: 7982

Answers (2)

Didac Montero
Didac Montero

Reputation: 2086

If you are using scala 2.13+ you can use Ordering.by and orElseBy. It is quite explicit.

case class Person(first: String, middle: String, last: String)

implicit val ordering: Ordering[Person] = Ordering.by[Person, String](_.first)
   .orElseBy(_.middle)
   .orElseBy(_.last)

val list = List(Person("john", "a", "smith"), Person("steve", "x", "scott"), Person("bill", "w", "smith"))

list.sorted

Upvotes: 8

dhg
dhg

Reputation: 52681

Option 1: sortBy

Using the sortBy method, this can be pretty simple:

case class Person(first: String, middle: String, last: String)
val personList = List(Person("john", "a", "smith"), Person("steve", "x", "scott"), Person("bill", "w", "smith"))
personList.sortBy{ case Person(f,m,l) => (l,f,m) } 

Option 2: Extend Ordered[Person]

By extending Ordered[Person], the class will know how to sort itself, so we get things like sorted, min, and max for free:

case class Person(first: String, middle: String, last: String) extends Ordered[Person] {
  def compare(that: Person): Int =
    (last compare that.last) match {
      case 0 => 
        (first compare that.first) match {
          case 0 => middle compare that.middle
          case c => c
        }
      case c => c
    }
}

val personList = List(Person("john", "a", "smith"), Person("steve", "x", "scott"), Person("bill", "w", "smith"))
personList.sorted
personList.min
personList.max

Option 3: Implicit Ordering

If you use an implicit Ordering, then you get sorted, min, etc without having that particular ordering tied to your original class. This decoupling might be convenient, or it might by annoying, depending on your specific case.

case class Person(first: String, middle: String, last: String)

implicit val ord = new Ordering[Person] { 
  def compare(self: Person, that: Person): Int =
    (self.last compare that.last) match {
      case 0 => 
        (self.first compare that.first) match {
          case 0 => self.middle compare that.middle
          case c => c
        }
      case c => c
    }
}

val personList = List(Person("john", "a", "smith"), Person("steve", "x", "scott"), Person("bill", "w", "smith"))
personList.sorted
personList.min
personList.max

Upvotes: 22

Related Questions