web-dev-qa-db-ja.com

単純なケースクラスの順序を定義する簡単な慣用的な方法

単純な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)
103
ya_pulser

私の個人的なお気に入りの方法は、明確で簡潔で正しいので、提供されている暗黙的なタプルの順序を使用することです:

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へのコンパニオン は、Orderedを実装するクラスのスコープ内にあるOrdering[T]からOrdered[T]への暗黙的な変換を定義するため、機能します。 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)はIDでソートされます。これにはいくつかの利点があります。

  • ソートは、目に見えるコード成果物として単一の場所で定義されます。これは、多くのフィールドに複雑な並べ替えがある場合に便利です。
  • scalaライブラリに実装されているほとんどのソート機能は、Orderingのインスタンスを使用して動作するため、ほとんどの場合、順序付けを直接提供することで暗黙的な変換を排除します。
140
J Cracknell
object A {
  implicit val ord = Ordering.by(unapply)
}

これには、Aが変更されるたびに自動的に更新されるという利点があります。ただし、Aのフィールドは、順序付けで使用される順序で配置する必要があります。

40
IttayD

要約すると、これを行うには3つの方法があります。

  1. @Shadowlandsが示したように、1回限りのソートには.sortByメソッドを使用します
  2. @Keithが言ったように、ソートの再利用のために、Orderedトレイトでケースクラスを拡張します。
  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))
    

あなたの質問に答えるList((2,1)、(1,2))。sortedのような魔法を行うことができるScalaに含まれる標準関数はありますか?

事前定義された順序 のセットがあります。文字列の場合、最大9個のアリティまでのタプルなど。

フィールド名が事前に知られていないため(少なくともマクロの魔法がなければ)ロールオフするのは簡単ではないので、ケースクラスにはそのようなものは存在しません。あなたは以外の方法でケースクラスフィールドにアクセスすることはできません名前/使用する製品イテレータ。

28
om-nom-nom

コンパニオンオブジェクトのunapplyメソッドは、ケースクラスからOption[Tuple]への変換を提供します。ここで、Tupleは、ケースクラスの最初の引数リストに対応するタプルです。言い換えると:

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

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

SortByメソッドは、これを行う典型的な方法の1つです。たとえば(tagフィールドでソート):

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

ケースクラスを使用したため、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
5
Keith Pinson