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

110

У меня есть список простых экземпляров класса case scala, и я хочу напечатать их в предсказуемом, лексикографическом порядке, используя list.sorted, но получаю сообщение «Неявный порядок определен для ...».

Существует ли неявный, обеспечивающий лексикографический порядок для классов 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)
ya_pulser
источник
8
Я только что написал на сообщение в блоге с парой общих решений здесь .
Трэвис Браун,

Ответы:

152

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

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. Наличие неявного Orderings для 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, поэтому упорядочивание напрямую исключает неявное преобразование в большинстве случаев.
Дж. Кракнелл
источник
Ваш пример замечательный! Однострочный и у меня порядок по умолчанию. Большое спасибо.
ya_pulser
7
Вариант класса A, предложенный в ответе, похоже, не компилируется под scala 2.10. Я что-то упускаю?
Дорон Яакоби
3
@DoronYaacoby: Я тоже получаю ошибку value compare is not a member of (String, Int).
bluenote10
1
@JCracknell Ошибка сохраняется даже после импорта (Scala 2.10.4). Ошибка возникает во время компиляции, но не отмечается в IDE. (что интересно, в REPL он работает правильно). Для тех, у кого есть эта проблема, решение этого SO-ответа работает (хотя и не так элегантно, как указано выше). Если это ошибка, кто-нибудь сообщил об этом?
Jus12
2
ИСПРАВЛЕНИЕ: Объем упорядочивания не втягивается, вы можете неявно втянуть его, но достаточно просто просто использовать упорядочивание напрямую: def compare (that: A) = Ordering.Tuple2 [String, String] .compare (tuple (this ), tuple (that))
brendon
46
object A {
  implicit val ord = Ordering.by(unapply)
}

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

IttayD
источник
Выглядит круто, но я не могу понять, как это использовать, получаю:<console>:12: error: not found: value unapply
zbstof
29

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

  1. Для одноразовой сортировки используйте метод .sortBy, как показал @Shadowlands
  2. Для повторного использования сортировки расширьте класс 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)). Sorted

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

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

ом Ном ном
источник
Большое спасибо за примеры. Я постараюсь понять неявное упорядочивание.
ya_pulser
8

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

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)
Shadowlands
источник
Что делать, если в кейс-классе 3+ полей? l.sortBy( e => e._tag + " " + e._load + " " + ... )?
ya_pulser
Если используется sortBy, то да, либо это, либо добавить / использовать подходящую функцию в / в классе (например _.toString, или ваш собственный лексографически значимый пользовательский метод или внешняя функция).
Shadowlands
Есть ли в Scala какая-либо стандартная функция, которая может творить магию, как List((2,1),(1,2)).sortedс объектами класса case? Я не вижу большой разницы между именованными кортежами (case class == named tuple) и простыми кортежами.
ya_pulser
Ближайшее, что я могу сделать по этим строкам, - это использовать метод неприменения сопутствующего объекта, чтобы получить Option[TupleN], а затем вызвать его get:, l.sortBy(A.unapply(_).get)foreach(println)который использует предоставленный порядок в соответствующем кортеже, но это просто явный пример общей идеи, которую я привожу выше .
Shadowlands
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
Кейт Пинсон
источник