115

我有一个简单的 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)
4

7 回答 7

169

我个人最喜欢的方法是使用提供的元组隐式排序,因为它清晰、简洁且正确:

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]它在任何实现的类的范围内Ordered。对于 s的隐式Orderings的存在使得从到Tuple的转换能够为元组的所有元素提供隐式存在,这应该总是如此,因为对没有的数据类型进行排序是没有意义的。TupleN[...]Ordered[TupleN[...]]Ordering[TN]T1, ..., TNOrdering

Tuples 的隐式排序是任何涉及复合排序键的排序方案的首选:

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

Given es: SeqLike[Employee]es.sorted()将按名称排序,es.sorted(Employee.orderingById)并将按 id 排序。这有几个好处:

  • 排序在单个位置定义为可见的代码工件。如果您在许多字段上有复杂的排序,这很有用。
  • scala 库中实现的大多数排序功能都使用 的实例进行操作Ordering,因此在大多数情况下提供排序直接消除了隐式转换。
于 2013-10-13T17:53:10.177 回答
51
object A {
  implicit val ord = Ordering.by(unapply)
}

这样做的好处是它会在 A 更改时自动更新。但是,A 的字段需要按照排序使用它们的顺序放置。

于 2014-07-01T07:42:14.973 回答
29

总而言之,有三种方法可以做到这一点:

  1. 对于一次性排序使用 .sortBy 方法,正如@Shadowlands 所展示的
  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)).sorted 那样做魔法

有一组预定义的排序,例如对于字符串、最多 9 个元组的元组等等。

案例类不存在这样的东西,因为滚动并不容易,因为字段名称是先验未知的(至少没有宏魔术),并且您不能以其他方式访问案例类字段名称/使用产品迭代器。

于 2013-10-13T15:02:42.910 回答
8

unapply方法提供了从案例类到 的转换Option[Tuple],其中Tuple是对应于案例类的第一个参数列表的元组。换句话说:

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

def sortPeople(people : List[Person]) = 
    people.sortBy(Person.unapply)
于 2015-01-29T10:43:52.037 回答
7

sortBy 方法将是执行此操作的一种典型方式,例如(按tag字段排序):

scala> l.sortBy(_.tag)foreach(println)
A(article,2)
A(lines,7)
A(words,50)
于 2013-10-13T12:14:13.853 回答
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
于 2013-10-13T13:08:56.647 回答
0

我个人最喜欢的方法是使用带有 2.12 的 SAM(单一抽象方法),如下例所述:

case class Team(city:String, mascot:String)

//Create two choices to sort by, city and mascot
object MyPredef3 {
  // Below used in 2.11
  implicit val teamsSortedByCity: Ordering[Team] = new Ordering[Team] {
    override def compare(x: Team, y: Team) = x.city compare y.city
  }

  implicit val teamsSortedByMascot: Ordering[Team] = new Ordering[Team] {
    override def compare(x: Team, y: Team) = x.mascot compare y.mascot
  }

  /*
     Below used in 2.12
     implicit val teamsSortedByCity: Ordering[Team] =
    (x: Team, y: Team) => x.city compare y.city
     implicit val teamsSortedByMascot: Ordering[Team] =
    (x: Team, y: Team) => x.mascot compare y.mascot

   */
}

object _6OrderingAList extends App {
  //Create some sports teams
  val teams = List(Team("Cincinnati", "Bengals"),
    Team("Madrid", "Real Madrid"),
    Team("Las Vegas", "Golden Knights"),
    Team("Houston", "Astros"),
    Team("Cleveland", "Cavaliers"),
    Team("Arizona", "Diamondbacks"))

  //import the implicit rule we want, in this case city
  import MyPredef3.teamsSortedByCity

  //min finds the minimum, since we are sorting
  //by city, Arizona wins.
  println(teams.min.city)

}
于 2021-04-30T06:54:17.300 回答