为一个简单的 case 类定义 Ording 的简单惯用方法

我有一个简单的 scala case 类实例的列表,我想用可预测的字典序打印它们,但是接收到“没有为... 定义的隐式排序”。

是否存在为案例类提供词典学排序的隐式方法?

是否有一种简单的惯用方法将词典排序混入格类中?

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)
81866 次浏览

SortBy 方法是这样做的一种典型方法,例如(在 tag字段上进行排序) :

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

因为你使用的是 案例类,所以你可以像这样用 命令扩展:

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

To summarize, there are three ways to do this:

  1. 对于一次性排序,使用. sortBy 方法,如@Shadowlands 所示
  2. 如@Keith 所说,用 Orderedtrait 重用排序扩展案例类。
  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))
    

Answering your question Is there any standard function included into the Scala that can do magic like List((2,1),(1,2)).sorted

There is a set of predefined orderings, e.g. for String, tuples up to 9 arity and so on.

No such thing exists for case classes, since it is not easy thing to roll off, given that field names are not known a-priori (at least without macros magic) and you can't access case class fields in a way other than by name/using product iterator.

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

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

这是因为 Ordering[T]1定义了一个从 Ordering[T]Ordered[T]的隐式转换,这个隐式转换适用于任何实现 Ordered的类。Tuple的隐式 Ordering的存在使得从 TupleN[...]Ordered[TupleN[...]]的转换成为可能,前提是元组的所有元素 T1, ..., TN都存在隐式 Ordering[TN],这应该总是如此,因为在没有 Ordering的数据类型上排序是没有意义的。

The implicit ordering for Tuples is your go-to for any sorting scenario involving a composite sort key:

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

由于这个答案已经被证明是流行的,我想对它进行扩展,注意到在某些情况下,类似于以下的解决方案可以被认为是企业级 TM:

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() will sort by name, and es.sorted(Employee.orderingById) will sort by id. This has a few benefits:

  • 排序在单个位置定义为可见代码构件。如果您在许多字段上有复杂的排序,那么这是非常有用的。
  • Scala 库中实现的大多数排序功能都使用 Ordering实例进行操作,因此在大多数情况下,直接提供排序可以消除隐式转换。
object A {
implicit val ord = Ordering.by(unapply)
}

这样做的好处是,只要 A 发生变化,它就会自动更新。但是,A 的字段需要按照排序使用它们的顺序排列。

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

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


def sortPeople(people : List[Person]) =
people.sortBy(Person.unapply)

我个人最喜欢的方法是使用 SAM (单一抽象方法)和2.12,正如下面的例子所提到的:

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)


}