Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Ordered by multiple fields

Tags:

scala

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.

like image 479
Todd O'Bryan Avatar asked May 15 '12 14:05

Todd O'Bryan


2 Answers

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

like image 145
Didac Montero Avatar answered Nov 12 '22 01:11

Didac Montero


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
like image 42
dhg Avatar answered Nov 12 '22 01:11

dhg