J'ai une liste d'instances de classe scala simples) et je souhaite les imprimer dans un ordre lexicographique prévisible en utilisant list.sorted
, mais recevez le message "Aucun ordre implicite défini pour ...".
Existe-t-il un système implicite fournissant un ordre lexicographique pour les classes de cas?
Existe-t-il un moyen idiomatique simple d’intégrer un ordre lexicographique à une classe de cas?
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)
^
Je ne suis pas content d'un 'bidouillage':
scala> l.map(_.toString).sorted.foreach(println)
A(article,2)
A(lines,7)
A(words,50)
Ma méthode préférée personnelle consiste à utiliser la commande implicite fournie pour Tuples, car elle est claire, concise et correcte:
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)
}
Cela fonctionne parce que le compagnon de Ordered
définit une conversion implicite de Ordering[T]
Vers Ordered[T]
, Qui s'applique à toute classe implémentant Ordered
. L’existence de Ordering
s implicites pour Tuple
s permet une conversion de TupleN[...]
Vers Ordered[TupleN[...]]
, À condition qu’il existe un Ordering[TN]
Implicite pour tous les éléments T1, ..., TN
Du tuple, ce qui devrait toujours être le cas car il n’a aucun sens de trier sur un type de données sans Ordering
.
La commande implicite pour Tuples est votre choix pour tout scénario de tri impliquant une clé de tri composite:
as.sortBy(a => (a.tag, a.load))
Comme cette réponse a fait ses preuves, je voudrais développer, notant qu'une solution semblable à celle-ci pourrait dans certaines circonstances être considérée comme une solution de niveau entreprise ™:
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)
}
Étant donné es: SeqLike[Employee]
, es.sorted()
va trier par nom et es.sorted(Employee.orderingById)
va trier par id. Cela a quelques avantages:
Ordering
], si bien que fournir un ordre élimine directement une conversion implicite.object A {
implicit val ord = Ordering.by(unapply)
}
Cela a l'avantage d'être mis à jour automatiquement chaque fois que A change. Mais, les champs de A doivent être placés dans l'ordre dans lequel l'ordre les utilisera.
Pour résumer, il y a trois façons de le faire:
Définir une commande personnalisée. L'avantage de cette solution est que vous pouvez réutiliser des commandes et disposer de plusieurs façons de trier les instances de la même classe:
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))
Répondre à votre question Existe-t-il une fonction standard incluse dans le Scala) qui peut effectuer des opérations magiques telles que List ((2,1), (1,2)). trié
Il existe un ensemble de ordres prédéfinis , par exemple. pour String, tuples jusqu’à 9 arity et ainsi de suite.
Cela n’existe pas pour les classes de cas, car il n’est pas facile à remplacer, étant donné que les noms de champs ne sont pas connus a priori (du moins sans magie de macros) et que vous ne pouvez pas accéder aux champs de classes de cas autrement que par nom/utilisation de l'itérateur de produit.
La méthode unapply
de l'objet compagnon fournit une conversion de votre classe de cas en un fichier Option[Tuple]
, où Tuple
est le tuple correspondant à la première liste d'arguments de la classe de cas. En d'autres termes:
case class Person(name : String, age : Int, email : String)
def sortPeople(people : List[Person]) =
people.sortBy(Person.unapply)
La méthode sortBy serait un moyen typique de le faire, par exemple (trier sur le champ tag
):
scala> l.sortBy(_.tag)foreach(println)
A(article,2)
A(lines,7)
A(words,50)
Puisque vous avez utilisé une classe de cas, vous pouvez étendre avec commandé comme ceci:
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