web-dev-qa-db-fra.com

Moyen idiomatique facile de définir un ordre pour une classe de cas simple

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

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:

  • Les tris sont définis dans un seul emplacement en tant qu'artefacts de code visibles. Ceci 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 à l'aide d'instances de Ordering], si bien que fournir un ordre élimine directement une conversion implicite.
140
J Cracknell
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.

40
IttayD

Pour résumer, il y a trois façons de le faire:

  1. Pour le tri ponctuel, utilisez la méthode .sortBy, comme @Shadowlands l’a montré
  2. Pour réutiliser le tri, étendez la classe de cas avec le trait Ordered, comme l'a dit @ Keith.
  3. 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.

28
om-nom-nom

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

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

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