简单的惯用方法来定义简单案例类的订购

时间:2013-10-13 12:09:56

标签: scala sorting case-class

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

7 个答案:

答案 0 :(得分:137)

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

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

这是有效的,因为companion to Ordered定义了从Ordering[T]Ordered[T]的隐式转换,该转换适用于任何实现Ordered的类。 Ordering s的隐式Tuple s的存在允许从TupleN[...]转换为Ordered[TupleN[...]],前提是所有元素Ordering[TN]都存在隐式T1, ..., TN元组,应始终如此,因为对没有Ordering的数据类型进行排序是没有意义的。

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

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

鉴于es: SeqLike[Employee]es.sorted()将按名称排序,es.sorted(Employee.orderingById)将按ID排序。这有一些好处:

  • 排序在单个位置定义为可见代码工件。如果您在许多字段上有复杂的排序,这将非常有用。
  • scala库中实现的大多数排序功能都使用Ordering的实例进行操作,因此在大多数情况下直接提供排序会消除隐式转换。

答案 1 :(得分:36)

object A {
  implicit val ord = Ordering.by(unapply)
}

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

答案 2 :(得分:26)

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

  1. 对于一次性排序,请使用.sortBy方法,如@Shadowlands所示
  2. 重新使用Ordered trait重新排序扩展案例类,如@Keith所说。
  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))
    
  4. 回答你的问题 Scala中是否包含任何标准函数,可以像List((2,1),(1,2))那样做魔术。排序

    有一组predefined orderings,例如for String,元组最多9个等等。

    对于案例类没有这样的东西,因为滚动是不容易的,因为字段名称不是先验的(至少没有宏魔法)并且你不能以某种方式访问​​案例类字段除了名字/使用产品迭代器。

答案 3 :(得分:7)

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

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

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

答案 4 :(得分:6)

sortBy方法是一种典型的方法,例如(在tag字段上排序):

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

答案 5 :(得分: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

答案 6 :(得分:0)

我个人最喜欢的方法是使用 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)

}