У меня есть список простых экземпляров класса 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)
Мой личный любимый метод - использовать предоставленный неявный порядок для кортежей, поскольку он ясен, лаконичен и корректен:
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. Это имеет несколько преимуществ:
Ordering
, поэтому обеспечение упорядочения напрямую исключает неявное преобразование в большинстве случаев.protected def sortKey
.
object A {
implicit val ord = Ordering.by(unapply)
}
Это имеет то преимущество, что оно обновляется автоматически всякий раз, когда изменяется A. Но поля A должны быть размещены в порядке, в котором будет использоваться их порядок.
Подводя итог, можно сделать три способа:
Определите пользовательский порядок. Преимущество этого решения заключается в том, что вы можете повторно использовать порядок и иметь несколько способов сортировки экземпляров одного и того же класса:
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 таким образом кроме имени/использования итератора продукта.
Метод unapply
объекта-компаньона обеспечивает преобразование из вашего класса case в 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)
l.sortBy( e => e._tag + " " + e._load + " " + ... )
?
sortBy
, то да, либо добавьте / используйте подходящую функцию для / в классе (например, _.toString
, либо свой собственный лексографически значимый пользовательский метод или внешнюю функцию).
Так как вы использовали класс 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