Простой идиоматический способ определения порядка для простого кейс-класса

У меня есть список простых экземпляров классов дел Scala, и я хочу напечатать их в предсказуемом лексикографическом порядке, используя list.sorted, но получить "Не определено неявное упорядочение для...".

Существует ли неявное условие, обеспечивающее лексикографическое упорядочение для тематических классов?

Есть ли простой идиоматический способ смешать лексикографическое упорядочение в классе случая?

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)
            ^

Я не доволен "взломать"

scala> l.map(_.toString).sorted.foreach(println)
A(article,2)
A(lines,7)
A(words,50)

7 ответов

Решение

Мой личный любимый метод - использовать предоставленный неявный порядок для кортежей, поскольку он ясен, лаконичен и корректен:

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)
}

Это работает, потому что компаньонOrdered определяет неявное преобразование из Ordering[T] в Ordered[T] который находится в области применения для любого класса реализации Ordered, Существование неявного Orderingс для Tuples позволяет преобразование из TupleN[...] в Ordered[TupleN[...]] при условии неявного Ordering[TN] существует для всех элементов T1, ..., TN кортежа, который всегда должен иметь место, потому что нет смысла сортировать по типу данных без Ordering,

Неявное упорядочение для кортежей - это ваш путь к любому сценарию сортировки, включающему составной ключ сортировки:

as.sortBy(a => (a.tag, a.load))

Поскольку этот ответ оказался популярным, я хотел бы расширить его, отметив, что решение, похожее на следующее, в некоторых случаях можно считать корпоративным классом ™:

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)
}

Дано es: SeqLike[Employee], es.sorted() будет сортировать по имени, и es.sorted(Employee.orderingById) будет сортировать по идентификатору. Это имеет несколько преимуществ:

  • Сортировки определяются в одном месте как видимые артефакты кода. Это полезно, если у вас есть сложные сортировки на многих полях.
  • Большинство функций сортировки, реализованных в библиотеке scala, работают с использованием экземпляров Orderingтаким образом, предоставление порядка напрямую исключает неявное преобразование в большинстве случаев.
object A {
  implicit val ord = Ordering.by(unapply)
}

Преимущество в том, что оно обновляется автоматически при изменении A. Но поля A должны быть расположены в том порядке, в котором они будут использованы в порядке.

Подводя итог, есть три способа сделать это:

  1. Для одноразовой сортировки используйте метод.sortBy, как показало @Shadowlands
  2. Для повторного использования сортировки расширьте case case с помощью свойства Ordered, как сказал @Keith.
  3. Определите индивидуальный заказ. Преимущество этого решения заключается в том, что вы можете повторно использовать заказы и иметь несколько способов сортировки экземпляров одного и того же класса:

    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))
    

Ответ на ваш вопрос Есть ли в Scala стандартная функция, которая может творить магию, например List((2,1),(1,2)).

Существует набор предопределенных порядков, например, для String, кортежей до 9 арностей и так далее.

Для классов прецедентов такой вещи не существует, так как откатить это непросто, учитывая, что имена полей не известны априори (по крайней мере, без магии макросов), и вы не можете получить доступ к полям классов прецедентов иначе, чем имя / использование итератора продукта.

unapply Метод сопутствующего объекта обеспечивает преобразование из вашего класса дела в Option[Tuple], где Tuple это кортеж, соответствующий первому списку аргументов класса case. Другими словами:

case class Person(name : String, age : Int, email : String)

def sortPeople(people : List[Person]) = 
    people.sortBy(Person.unapply)

Метод sortBy будет одним из типичных способов сделать это, например, (сортировка по tag поле):

scala> l.sortBy(_.tag)foreach(println)
A(article,2)
A(lines,7)
A(words,50)

Поскольку вы использовали класс case, вы можете расширить его с помощью Ordered следующим образом:

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

Мой личный любимый метод - это использование SAM(метод одиночной абстракции) с 2.12, как указано в приведенном ниже примере:

      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)

}
Другие вопросы по тегам