Einfache idiomatische Definition der Reihenfolge für eine einfache Fallklasse

110

Ich habe eine Liste einfacher Scala-Fallklasseninstanzen und möchte sie in vorhersehbarer, lexikografischer Reihenfolge mit drucken list.sorted, erhalte jedoch "Keine implizite Reihenfolge definiert für ...".

Gibt es ein Implizit, das eine lexikografische Reihenfolge für Fallklassen vorsieht?

Gibt es eine einfache idiomatische Möglichkeit, die lexikografische Reihenfolge in die Fallklasse einzumischen?

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)
            ^

Ich bin nicht glücklich mit einem "Hack":

scala> l.map(_.toString).sorted.foreach(println)
A(article,2)
A(lines,7)
A(words,50)
ya_pulser
quelle
8
Ich habe gerade einen Blog-Beitrag mit ein paar generischen Lösungen hier geschrieben .
Travis Brown

Antworten:

152

Meine persönliche Lieblingsmethode besteht darin, die bereitgestellte implizite Reihenfolge für Tupel zu verwenden, da diese klar, präzise und korrekt ist:

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)
}

Das funktioniert , weil der BegleiterOrdered definiert eine implizite Konvertierung von Ordering[T]zu Ordered[T]denen in Rahmen für jede Klasse ist die Umsetzung Ordered. Das Vorhandensein von impliziten Orderings für Tuples ermöglicht eine Konvertierung von TupleN[...]zu, Ordered[TupleN[...]]sofern Ordering[TN]für alle Elemente T1, ..., TNdes Tupels ein implizites s vorhanden ist. Dies sollte immer der Fall sein, da es keinen Sinn macht, nach einem Datentyp mit no zu sortieren Ordering.

Die implizite Reihenfolge für Tupel ist Ihre Anlaufstelle für jedes Sortierszenario mit einem zusammengesetzten Sortierschlüssel:

as.sortBy(a => (a.tag, a.load))

Da sich diese Antwort als beliebt erwiesen hat, möchte ich darauf eingehen und darauf hinweisen, dass eine Lösung, die der folgenden ähnelt, unter bestimmten Umständen als Enterprise-Grade ™ angesehen werden kann:

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)
}

Gegeben es: SeqLike[Employee], es.sorted()wird nach Namen und es.sorted(Employee.orderingById)nach ID sortiert. Dies hat einige Vorteile:

  • Die Sortierungen werden an einer einzelnen Stelle als sichtbare Code-Artefakte definiert. Dies ist nützlich, wenn Sie in vielen Feldern komplexe Sortierungen haben.
  • Die meisten in der Scala-Bibliothek implementierten Sortierfunktionen werden mit Instanzen von ausgeführt Ordering. Wenn Sie also eine Bestellung direkt bereitstellen, wird in den meisten Fällen eine implizite Konvertierung vermieden.
J Cracknell
quelle
Ihr Beispiel ist wunderbar! Single-Liner und ich haben Standardbestellung. Vielen Dank.
ya_pulser
7
Die in der Antwort vorgeschlagene Fallklasse A scheint unter Scala 2.10 nicht kompiliert zu werden. Vermisse ich etwas
Doron Yaacoby
3
@ DoronYaacoby: Ich bekomme auch eine Fehlermeldung value compare is not a member of (String, Int).
bluenote10
1
@JCracknell Der Fehler ist auch nach dem Import noch vorhanden (Scala 2.10.4). Beim Kompilieren tritt ein Fehler auf, der jedoch in der IDE nicht markiert wird. (Interessanterweise funktioniert es in REPL ordnungsgemäß). Für diejenigen, die dieses Problem haben, funktioniert die Lösung bei dieser SO-Antwort (wenn auch nicht so elegant wie oben). Wenn es sich um einen Fehler handelt, hat ihn jemand gemeldet?
Jus12
2
UPDATE: Der Umfang der Bestellung wird nicht eingezogen, Sie können ihn implizit einlesen, aber es ist einfach genug, die Bestellung direkt zu verwenden: def compare (that: A) = Ordering.Tuple2 [String, String] .compare (tuple (this) ), Tupel (das))
brendon
46
object A {
  implicit val ord = Ordering.by(unapply)
}

Dies hat den Vorteil, dass es automatisch aktualisiert wird, wenn sich A ändert. Die Felder von A müssen jedoch in der Reihenfolge platziert werden, in der die Bestellung sie verwendet.

IttayD
quelle
Sieht cool aus, aber ich kann nicht herausfinden, wie man das benutzt. Ich bekomme:<console>:12: error: not found: value unapply
zbstof
29

Zusammenfassend gibt es drei Möglichkeiten, dies zu tun:

  1. Verwenden Sie für die einmalige Sortierung die Methode .sortBy, wie @Shadowlands gezeigt hat
  2. Erweitern Sie für die Wiederverwendung der Sortierung die Fallklasse mit dem Merkmal "Geordnet", wie @Keith sagte.
  3. Definieren Sie eine benutzerdefinierte Bestellung. Der Vorteil dieser Lösung besteht darin, dass Sie Bestellungen wiederverwenden und Instanzen derselben Klasse auf mehrere Arten sortieren können:

    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))

Beantwortung Ihrer Frage Gibt es eine Standardfunktion in der Scala, die wie List ((2,1), (1,2)) zaubern kann? Sortiert

Es gibt eine Reihe vordefinierter Ordnungen , z. B. für String, Tupel bis zu 9 Arity und so weiter.

Für Fallklassen gibt es so etwas nicht, da das Abrollen nicht einfach ist, da Feldnamen nicht a priori bekannt sind (zumindest ohne Makromagie) und Sie nicht auf andere Weise als durch auf Fallklassenfelder zugreifen können Name / mit Produktiterator.

OM Nom Nom
quelle
Vielen Dank für die Beispiele. Ich werde versuchen, die implizite Reihenfolge zu verstehen.
ya_pulser
8

Die unapplyMethode des Begleitobjekts bietet eine Konvertierung von Ihrer Fallklasse in eine Option[Tuple], wobei das Tupledas Tupel ist, das der ersten Argumentliste der Fallklasse entspricht. Mit anderen Worten:

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

def sortPeople(people : List[Person]) = 
    people.sortBy(Person.unapply)
Joakim Ahnfelt-Rønne
quelle
6

Die sortBy-Methode wäre eine typische Methode, z. B. (Sortieren nach tagFeld):

scala> l.sortBy(_.tag)foreach(println)
A(article,2)
A(lines,7)
A(words,50)
Schattenlande
quelle
Was tun bei 3+ Feldern in der Fallklasse? l.sortBy( e => e._tag + " " + e._load + " " + ... )?
ya_pulser
Wenn Sie verwenden sortBy, dann ja, entweder das, oder fügen Sie der Klasse eine geeignete Funktion hinzu / verwenden Sie sie (z. B. _.toStringIhre eigene lexografisch signifikante benutzerdefinierte Methode oder externe Funktion).
Shadowlands
Ist in der Scala eine Standardfunktion enthalten, die wie List((2,1),(1,2)).sorteddie Objekte der Fallklasse zaubern kann ? Ich sehe keinen großen Unterschied zwischen benannten Tupeln (case class == named tuple) und einfachen Tupeln.
ya_pulser
Die nächst ich in diese Richtung bekommen kann , ist der Begleiter des Objekts Unapply Methode zu verwenden , um ein zu bekommen Option[TupleN], dann rufen Sie getauf , dass: l.sortBy(A.unapply(_).get)foreach(println), die die vorgesehene Reihenfolge auf dem entsprechende Tupel verwendet, aber dies ist nur ein explizites Beispiel für die allgemeine Idee , die ich oben geben .
Shadowlands
5

Da Sie eine Fallklasse verwendet haben, können Sie mit Ordered wie folgt erweitern:

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
Keith Pinson
quelle