【问题标题】:Extending Seq.sortBy in Scala在 Scala 中扩展 Seq.sortBy
【发布时间】:2011-06-29 17:34:32
【问题描述】:

假设我有一个名字列表。

case class Name(val first: String, val last: String)

val names = Name("c", "B") :: Name("b", "a") :: Name("a", "B") :: Nil

如果我现在想按姓氏对列表进行排序(如果这还不够,则按名字),这很容易做到。

names.sortBy(n => (n.last, n.first))
// List[Name] = List(Name(a,B), Name(c,B), Name(b,a))

但是,如果我想根据字符串的其他排序规则对该列表进行排序呢?

很遗憾,以下方法不起作用:

val o  = new Ordering[String]{ def compare(x: String, y: String) = collator.compare(x, y) }
names.sortBy(n => (n.last, n.first))(o)
// error: type mismatch;
// found   : java.lang.Object with Ordering[String]
// required: Ordering[(String, String)]
//   names.sortBy(n => (n.last, n.first))(o)

有什么方法可以让我更改排序,而无需编写具有多个 ifelse 分支的显式 sortWith 方法来处理所有情况?

【问题讨论】:

    标签: sorting scala collation


    【解决方案1】:

    一种解决方案是扩展其他隐式使用的 Tuple2 排序。不幸的是,这意味着在代码中写出Tuple2

    names.sortBy(n => (n.second, n.first))(Ordering.Tuple2(o, o))
    

    【讨论】:

      【解决方案2】:

      我不是 100% 确定你认为 collator 应该有什么方法。

      但是,如果您在案例类上定义排序,您将拥有最大的灵活性:

      val o = new Ordering[Name]{
        def compare(a: Name, b: Name) =
          3*math.signum(collator.compare(a.last,b.last)) +
          math.signum(collator.compare(a.first,b.first))
      }
      names.sorted(o)
      

      但您也可以提供从字符串排序到名称排序的隐式转换:

      def ostring2oname(os: Ordering[String]) = new Ordering[Name] {
        def compare(a: Name, b: Name) = 
          3*math.signum(os.compare(a.last,b.last)) + math.signum(os.compare(a.first,b.first))
      }
      

      然后您可以使用任何字符串排序来对名称进行排序:

      def oo = new Ordering[String] {
        def compare(x: String, y: String) = x.length compare y.length
      }
      val morenames = List("rat","fish","octopus")
      
      scala> morenames.sorted(oo)
      res1: List[java.lang.String] = List(rat, fish, octopus)
      

      编辑:一个方便的技巧,以防它不明显,如果你想按 N 个东西排序并且你已经在使用比较,你可以将每个东西乘以 3^k(第一个 - to-order 乘以 3) 的最大幂并相加。


      如果您的比较非常耗时,您可以轻松添加级联比较:

      class CascadeCompare(i: Int) {
        def tiebreak(j: => Int) = if (i!=0) i else j
      }
      implicit def break_ties(i: Int) = new CascadeCompare(i)
      

      然后

      def ostring2oname(os: Ordering[String]) = new Ordering[Name] {
        def compare(a: Name, b: Name) =
          os.compare(a.last,b.last) tiebreak os.compare(a.first,b.first)
      }
      

      (小心嵌套它们x tiebreak ( y tiebreak ( z tiebreak w ) ) ),这样你就不会连续多次进行隐式转换)。

      (如果您真的需要快速比较,那么您应该手动将其全部写出来,或者将排序打包到一个数组中并使用 while 循环。我假设您并不那么渴望性能。)

      【讨论】:

      • 嗯,我不知道。乘以 3^k 看起来很聪明,但如果某些东西不遵循 -1/0/+1 约定,它当然不会起作用。此外,它同时评估所有比较,而元组比较在发现差异时停止。
      • @Debilski - 哎呀,忘了math.signum 包装!是的,它确实进行了完整的评估——我同意,这对速度来说并不理想。
      • 我认为collatorjava.text.Collator 的一个实例。
      【解决方案3】:

      嗯,这几乎可以解决问题:

      names.sorted(o.on((n: Name) => n.last + n.first))
      

      另一方面,您也可以这样做:

      implicit val o  = new Ordering[String]{ def compare(x: String, y: String) = collator.compare(x, y) }
      names.sortBy(n => (n.last, n.first))
      

      这种本地定义的隐式将优先于Ordering 对象上定义的隐式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-07
        • 1970-01-01
        相关资源
        最近更新 更多