扩展Scala中的Seq.sortBy

时间:2011-02-18 10:21:15

标签: sorting scala collation

说我有一个名单。

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)

是否有任何方法可以让我更改排序而无需编写带有多个sortWith - if分支的显式else方法来处理所有情况?

3 个答案:

答案 0 :(得分:4)

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

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对象上定义的隐式。

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

我不是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(第一个 - 按顺序乘以最大幂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循环。我会假设你不是那种绝望的表现。)