Простой идиоматический способ определения порядка для простого кейс-класса
У меня есть список простых экземпляров классов дел 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
с для Tuple
s позволяет преобразование из 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 должны быть расположены в том порядке, в котором они будут использованы в порядке.
Подводя итог, есть три способа сделать это:
- Для одноразовой сортировки используйте метод.sortBy, как показало @Shadowlands
- Для повторного использования сортировки расширьте case case с помощью свойства Ordered, как сказал @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)).
Существует набор предопределенных порядков, например, для 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)
}