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

95

У меня есть список простых экземпляров класса case scala, и я хочу напечатать их в предсказуемом, лексикографическом порядке с помощью list.sorted, но получить "No implicit Ordering defined for...".

Существует ли неявный, который обеспечивает лексикографическое упорядочение для классов case?

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

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)
  • 8
    Я только что написал сообщение в блоге с парой общих решений здесь .
Теги:
sorting
case-class

6 ответов

137
Лучший ответ

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

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 для Tuple обеспечивает преобразование из 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) будет сортировать по id. Это имеет несколько преимуществ:

  • Сортировки определяются в одном месте как видимые артефакты кода. Это полезно, если у вас есть сложные сортировки на многих полях.
  • Большинство функций сортировки, реализованных в библиотеке scala, работают с использованием экземпляров Ordering, поэтому обеспечение упорядочения напрямую исключает неявное преобразование в большинстве случаев.
  • 0
    Ваш пример замечательный! Одиночный лайнер и у меня порядок по умолчанию. Большое спасибо.
  • 0
    Добро пожаловать! Я оставил свой пример простым для ясности, но в случаях, когда вы сортируете по многим полям, я бы, возможно, выделил извлечение ключа в protected def sortKey .
Показать ещё 10 комментариев
34
object A {
  implicit val ord = Ordering.by(unapply)
}

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

  • 1
    Это действительно гладко :)
23

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

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

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

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

Нет такой вещи для классов case, так как нелегко свернуть, учитывая, что имена полей неизвестны a-priori (по крайней мере, без магии макросов), и вы не можете получить доступ к полям класса case таким образом кроме имени/использования итератора продукта.

  • 0
    Большое спасибо за примеры. Я постараюсь понять неявный порядок.
6

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

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

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

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

scala> l.sortBy(_.tag)foreach(println)
A(article,2)
A(lines,7)
A(words,50)
  • 0
    Что делать в случае 3+ полей в случае класса? l.sortBy( e => e._tag + " " + e._load + " " + ... ) ?
  • 0
    Если вы используете sortBy , то да, либо добавьте / используйте подходящую функцию для / в классе (например, _.toString , либо свой собственный лексографически значимый пользовательский метод или внешнюю функцию).
Показать ещё 3 комментария
5

Так как вы использовали класс 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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню