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


110

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

8
私はここにいくつかの一般的な解決策をブログ記事に書いたところです。
Travis Brown

回答:


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]どれが実装任意のクラスの範囲内にありますOrderedOrderings の暗黙のs の存在により、タプルのすべての要素に暗黙の存在が提供されるTupleからTupleN[...]への変換が可能になります。Ordered[TupleN[...]]Ordering[TN]T1, ..., TNOrdering

タプルの暗黙的な順序付けは、複合ソートキーが関係するすべてのソートシナリオに最適です。

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でソートされます。これにはいくつかの利点があります。

  • ソートは、可視コードアーティファクトとして1つの場所で定義されます。これは、多くのフィールドで複雑なソートがある場合に役立ちます。
  • scalaライブラリに実装されているほとんどの並べ替え機能はのインスタンスを使用して動作するOrderingため、順序付けを提供すると、ほとんどの場合、暗黙的な変換が直接排除されます。

あなたの例は素晴らしいです!シングルライナーと私はデフォルトの注文があります。どうもありがとうございました。
ya_pulser 2013年

7
回答で提案されているケースクラスAは、Scala 2.10ではコンパイルされないようです。何か不足していますか?
Doron Yaacoby 2014

3
@DoronYaacoby:エラーも発生しますvalue compare is not a member of (String, Int)
bluenote10 2014年

1
@JCracknellインポート後もエラーが残っています(Scala 2.10.4)。コンパイル中にエラーが発生しますが、IDEではフラグが立てられません。(興味深いことに、REPLでは適切に機能します)。この問題を抱えている人のために、このSO回答の解決策はうまくいきます(ただし、上記のようにエレガントではありません)。それがバグである場合、誰かがそれを報告しましたか?
Jus12 2014年

2
FIX:Orderingのスコープが引き込まれず、暗黙的に引き込むことができますが、直接Orderingを使用するだけで十分簡単です:def compare(that:A)= Ordering.Tuple2 [String、String] .compare(tuple(this )、tuple(that))
brendon 2016年

46
object A {
  implicit val ord = Ordering.by(unapply)
}

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


見た目はかっこいいですが、これを使用する方法がわかりません。次のようになります<console>:12: error: not found: value unapply
。– zbstof

29

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

  1. 1回限りの並べ替えには、.Shadowlandsが示したように、.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))

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

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

フィールド名がアプリオリに認識されておらず(少なくともマクロマジックがない場合)、以下以外の方法でケースクラスフィールドにアクセスできないことを考えると、ロールオフするのは簡単なことではないため、ケースクラスにはそのようなものはありません。名前/使用製品イテレータ。


例をありがとうございました。暗黙の順序付けを理解しようとします。
ya_pulser 2013年

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メソッドは、これを行う典型的な方法の1つです(例tag:(フィールドでソート))。

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

ケースクラスの3つ以上のフィールドの場合はどうしますか?l.sortBy( e => e._tag + " " + e._load + " " + ... )
ya_pulser 2013年

を使用している場合sortByは、はい、そのいずれか、またはクラスに適切な関数を追加/使用し_.toStringます(例:、または独自の辞書的に意味のあるカスタムメソッドまたは外部関数)。
Shadowlands 2013年

List((2,1),(1,2)).sortedケースクラスオブジェクトのように魔法をかけることができる標準関数がScalaに含まれていますか?名前付きタプル(ケースクラス==名前付きタプル)と単純なタプルの間に大きな違いはありません。
ya_pulser 2013年

私はそれらの線に沿って取得することができます最寄りは取得するコンパニオンオブジェクトの適用を解除する方法を使用することでOption[TupleN]、その後呼び出して、get:それにl.sortBy(A.unapply(_).get)foreach(println)対応するタプルの提供順序を使用していますが、これは単に私が上記与える一般的な考え方の明確な例です、 。
Shadowlands 2013年

5

ケースクラスを使用したので、次のように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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.