118 votes

Manière idiomatique et facile de définir l'ordonnancement pour une simple classe de cas

J'ai une liste d'instances de classes de cas scala simples et je souhaite les imprimer dans un ordre prévisible et lexicographique en utilisant list.sorted, mais je reçois "Aucun ordre implicite défini pour ...".

Existe-t-il un implicite qui fournit un ordre lexicographique pour les classes de cas?

Existe-t-il un moyen idiomatique simple d'ajouter un ordre lexicographique à une classe de cas?

scala> case class A(tag:String, load:Int)
scala> val l = List(A("mots",50),A("article",2),A("lignes",7))

scala> l.sorted.foreach(println)
:11: erreur: Aucun ordre implicite défini pour A.
          l.sorted.foreach(println)
            ^

Je ne suis pas satisfait d'un 'hack' :

scala> l.map(_.toString).sorted.foreach(println)
A(article,2)
A(lignes,7)
A(mots,50)

172voto

J Cracknell Points 852

Méthode personnelle préférée consiste à utiliser l'ordre implicite fourni pour les tuples, car c'est clair, concis et correct :

case class A(tag: String, load: Int) extends Ordered[A] {
  // Requis à partir de Scala 2.11 pour des raisons inconnues - le compagnon de Ordered
  // devrait déjà être dans la portée implicite
  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] à Ordered[T] qui est en portée pour toute classe implémentant Ordered. L'existence des Ordering implicits pour les Tuple permet une conversion de TupleN[...] à Ordered[TupleN[...]] à condition qu'un Ordering[TN] implicite existe pour tous les éléments T1, ..., TN du tuple, ce qui devrait toujours être le cas car il n'a pas de sens de trier sur un type de données sans Ordering.

L'ordre implicite pour les Tuples est ce qu'il faut utiliser pour tout scénario de tri impliquant une clé de tri composite :

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

Comme cette réponse a été populaire, j'aimerais l'élargir, notant qu'une solution ressemblant à ce qui suit pourrait dans certaines circonstances être considérée comme de qualité entreprise™ :

case class Employee(id: Int, firstName: String, lastName: String)

object Employee {
  // Notez que parce que `Ordering[A]` n'est pas contravariant, la déclaration
  // doit être paramétrée par type dans le cas où vous voulez que l'ordre implicite
  // s'applique aux sous-classes de `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() triera par nom, et es.sorted(Employee.orderingById) triera par identifiant. Cela présente quelques avantages :

  • Les tris sont définis en un seul endroit en tant qu'artefacts de code visibles. C'est utile si vous avez des tris complexes sur de nombreux champs.
  • La plupart des fonctionnalités de tri implémentées dans la bibliothèque scala fonctionnent en utilisant des instances de Ordering, donc fournir un ordre directement élimine une conversion implicite dans la plupart des cas.

51voto

IttayD Points 10490
objet A {
  val ord = Ordering.by(unapply)
}

Cela présente l'avantage d'être mis à jour automatiquement chaque fois que A change. Cependant, les champs de A doivent être placés dans l'ordre par lequel l'ordonnancement les utilisera.

29voto

om-nom-nom Points 33691

Pour résumer, il existe trois façons de faire cela :

  1. Pour un tri ponctuel, utilisez la méthode .sortBy, comme l'a montré @Shadowlands
  2. Pour réutiliser le tri, étendez la classe de cas avec le trait Ordered, comme l'a dit @Keith.
  3. Définissez un ordre personnalisé. L'avantage de cette solution est que vous pouvez réutiliser des ordres et avoir plusieurs façons de trier des 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("mots",1), A("article",2), A("lines",3)).sorted
    // List(A(article,2), A(lines,3), A(mots,1))
    
    // maintenant dans un autre contexte
    implicit val ord = A.loadOrdering
    val l = List(A("mots",1), A("article",2), A("lines",3)).sorted
    // List(A(mots,1), A(article,2), A(lines,3))

Répondant à votre question Y a-t-il une fonction standard incluse dans Scala qui peut faire de la magie comme List((2,1),(1,2)).triée

Il existe un ensemble de ordres prédéfinis, par exemple pour les String, les tuples jusqu'à 9 arités, etc.

Une telle chose n'existe pas pour les classes de cas, car ce n'est pas une chose facile à réaliser, étant donné que les noms de champs ne sont pas connus à l'avance (du moins sans magie de macros) et vous ne pouvez pas accéder aux champs de classe de cas de manière différente que par nom/en utilisant l'itérateur de produit.

8voto

La méthode unapply de l'objet compagnon fournit une conversion de votre classe de cas en un Option[Tuple], où le Tuple est le tuple correspondant au premier argument de la liste 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)

7voto

Shadowlands Points 4538

La méthode sortBy serait un moyen typique de faire cela, par exemple (tri sur le champ tag):

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X