【问题标题】:Elegant way to sort Array[B] for a subclass B < A, when A extends Ordered[A]?当 A 扩展 Ordered[A] 时,为子类 B < A 排序 Array[B] 的优雅方式?
【发布时间】:2012-08-29 04:56:55
【问题描述】:

定义了扩展 Ordering[A] 的类 A 和 A 的子类 B,如何自动对 B 数组进行排序? Scala 编译器抱怨它“找不到参数 ord: Ordering[B] 的隐含值”。这是一个具体的 REPL 示例(Scala 2.8),其中 A = Score 和 B = CommentedScore:

class Score(val value: Double) extends Ordered[Score] {
  def compare(that: Score) = value.compare(that.value)
}
defined class Score

trait Comment { def comment: String }
defined trait Comment

class CommentedScore(value: Double, val comment: String) extends Score(value) with Comment
defined class CommentedScore

val s = new CommentedScore(10,"great")
s: CommentedScore = CommentedScore@842f23

val t = new CommentedScore(0,"mediocre")
t: CommentedScore = CommentedScore@dc2bbe

val commentedScores = Array(s,t)
commentedScores: Array[CommentedScore] = Array(CommentedScore@b3f01d, CommentedScore@4f3c89)

util.Sorting.quickSort(commentedScores)
error: could not find implicit value for parameter ord: Ordering[CommentedScore]
       util.Sorting.quickSort(commentedScores)
                             ^

如何以优雅的方式解决此问题(即“免费”对 Array[B] = Array[CommentedScore] 进行排序,因为我知道如何对 Array[A] = Array[Score] 进行排序)哪个避免样板?

谢谢!

【问题讨论】:

    标签: scala boilerplate


    【解决方案1】:

    自己添加所需的隐式:

    implicit val csOrd: Ordering[CommentedScore] = Ordering.by(_.value)
    

    您可以将它放在 CommentedScore 伴随对象中,这样在使用站点就没有样板。


    编辑:如果您希望仅在继承树的顶部定义排序方法,您仍然需要为每个子类提供一个Ordering,但是您可以定义Orderingcompare 方法就Score 对象中的那个而言。即

    object Score {
      implicit val ord: Ordering[Score] = Ordering.by(_.value)
    }
    
    object CommentedScore {
      implicit val csOrd = new Ordering[CommentedScore] { 
        def compare(x: CommentedScore, y: CommentedScore) = Score.ord.compare(x, y)
      }
    }
    

    如果您不想为每个子类重新定义它,您可以使用泛型方法来生成Ordering

    object Score {
      implicit def ord[T <: Score]: Ordering[T] = Ordering.by(_.value)
    }
    

    这效率有点低,因为它是def 而不是val,每次需要时它都会创建一个新的Ordering。但是开销可能很小。另请注意,Ordered trait 和 compare 方法不再是必需的,现在我们有了 Orderings。

    【讨论】:

    • 谢谢。但我希望它依赖于分数排序:假设我稍后重构分数排序以按降序而不是递增值排序。我希望 CommentedScore 继承该更改。有什么想法吗?
    • 非常感谢,路易吉。不幸的是,我是 Scala 的新手,以至于我不知道“使用委托”是什么意思。如果不是太麻烦,您介意在这个Score 案例中说明您的意思吗?
    • @PerfectTiling 委托只是意味着将参数传递给另一个真正起作用的方法。已在上面进行了编辑。
    【解决方案2】:

    您可以使用来自scalaz 的Order,它是逆变的,因此您无需为每个子类定义它。这是一个例子:

    import scalaz._
    import Scalaz._
    
    class Score(val value: Double)
    object Score {
      implicit val scoreOrd: Order[Score] = orderBy(_.value)
    }
    trait Comment { def comment: String }
    class CommentedScore(value: Double, val comment: String) extends Score(value) with Comment {
      override def toString = s"cs($value, $comment)"
    } 
    def quickSort[E: Order](list: List[E]): List[E] = list match {
      case Nil => Nil
      case head :: tail =>
        val (less, more) = tail partition { e => implicitly[Order[E]].order(e, head) == LT }
        quickSort(less) ::: head :: quickSort(more) 
    }
    println(quickSort(List(
      new CommentedScore(10,"great"),
      new CommentedScore(5,"ok"),
      new CommentedScore(8,"nice"),
      new CommentedScore(0,"mediocre")
    ))) // List(cs(0.0, mediocre), cs(5.0, ok), cs(8.0, nice), cs(10.0, great))
    

    【讨论】:

    • 我一直认为 Ordering 应该是协变的。不过我还没有机会考虑清楚。
    【解决方案3】:

    这行得通:

    val scoreArray: Array[Score] = Array(s, t)
    util.Sorting.quickSort(scoreArray)
    

    或者如果你从Array[CommentedScore]开始:

    val scoreArray: Array[Score] = commentedScores.map(identity)
    util.Sorting.quickSort(scoreArray)
    

    请注意,您可以使用以下命令进行更简单的排序:

    scoreArray.sorted
    

    【讨论】:

    • 谢谢。实际上,我在数组上使用我自己的自定义部分排序算法。我把 util.Sorting.quickSort 放在这里。最后,我必须确保我有一个 Array[CommentedScore],所以我不想转换为 Arra[Score] 然后再转换为 Array[CommentedScore]。