【问题标题】:Easy idiomatic way to define Ordering for a simple case class为简单案例类定义 Ordering 的简单惯用方法
【发布时间】:2021-04-30 14:54:17
【问题描述】:

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

【问题讨论】:

  • 我刚刚写了一篇博文,其中包含几个通用解决方案here

标签: scala sorting case-class


【解决方案1】:

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

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 的类。 Tuples 的隐式 Orderings 的存在可以实现从 TupleN[...]Ordered[TupleN[...]] 的转换,前提是元组的所有元素 T1, ..., TN 都存在隐式 Ordering[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 的实例进行操作,因此在大多数情况下直接提供排序可以消除隐式转换。

【讨论】:

  • 你的例子太棒了!单班轮,我有默认订购。非常感谢。
  • 答案中建议的案例类 A 似乎无法在 scala 2.10 下编译。我错过了什么吗?
  • @DoronYaacoby:我也收到一个错误value compare is not a member of (String, Int)
  • @JCracknell 即使在导入后错误仍然存​​在(Scala 2.10.4)。编译期间发生错误,但未在 IDE 中标记。 (有趣的是,它在 REPL 中确实可以正常工作)。对于那些有这个问题的人,this SO answer 的解决方案可以工作(虽然没有上面的那么优雅)。如果是错误,有没有人报告过?
  • FIX:Ordering 的范围没有被拉入,您可以隐式拉入,但直接使用 Ordering 很容易:def compare(that: A) = Ordering.Tuple2[String, String].compare(tuple(this), tuple(that))
【解决方案2】:
object A {
  implicit val ord = Ordering.by(unapply)
}

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

【讨论】:

  • 看起来很酷,但我不知道如何使用它,我得到:&lt;console&gt;:12: error: not found: value unapply
【解决方案3】:

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

  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 一样发挥神奇作用

有一组predefined orderings,例如对于字符串,最多 9 个元组的元组等等。

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

【讨论】:

  • 非常感谢您提供的示例。我会尝试理解隐式排序。
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

    • case类中有3+字段怎么办? l.sortBy( e =&gt; e._tag + " " + e._load + " " + ... ) ?
    • 如果使用sortBy,那么可以,或者在类中添加/使用合适的函数(例如_.toString,或者您自己的具有字典意义的自定义方法或外部函数) .
    • Scala 中是否包含任何标准函数,可以像 List((2,1),(1,2)).sorted 一样对案例类对象施展魔法?我认为命名元组(案例 class== 命名元组)和简单元组之间没有太大区别。
    • 我最接近的方法是使用伴随对象的 unapply 方法获取Option[TupleN],然后调用getl.sortBy(A.unapply(_).get)foreach(println),它使用提供的排序对应的元组,但这只是我上面给出的一般想法的一个明确示例。
    【解决方案6】:

    由于您使用了 case 类,因此您可以使用 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
    

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        猜你喜欢
        • 2011-02-01
        • 2021-06-09
        • 1970-01-01
        • 2015-10-07
        • 2014-06-01
        • 2011-09-02
        • 1970-01-01
        • 1970-01-01
        • 2013-12-18
        相关资源
        最近更新 更多