20

我想做这样的事情:

class Foo extends Ordered[Foo] {
   val x
   val y
   val z
   .
   .
   .
   .
   def compare(that: Foo) = {
      val c0 = this.length compareTo that.length          // primary comparison
      lazy val c1 = this.x compareTo that.x               // secondary comparison
      lazy val c2 = this.y.size compareTo that.y.size     // tertiary comparison
      lazy val c3 = this.z.head compareTo that.z.head     // final tie breaker
      if (c0 != 0) c0 else if (c1 != 0) c1 else if (c2 != 0) c2 else if (c3 != 0) c3 else c4
   }    
}

我想知道是否有任何更清洁的方式来写这种东西。我期待像Ordering.multipleBy(ordering: Ordered[A]*)签名这样的东西,它采用可比较的可变参数并选择第一个非零。

4

5 回答 5

34

通常最好使用Ordering而不是Ordered. Ordering是一个类型类并且比Ordered(如果只是因为Ordered必须由类型实现来比较,而Ordering你可以在外面定义这个)灵活得​​多。要为您的类型定义自然排序(默认Ordering实例),您只需在伴随对象中定义一个隐式排序值。

所以,序言就足够了。好的是当使用Ordering你想做的事情时非常简单,因为元组有一个隐式排序(假设元组元素本身有一个排序)`:

object Foo {
  implicit val FooOrdering = Ordering.by{ foo: Foo => 
    (foo.length, foo.x, foo.y, foo.z) 
  }
}

此外,还有一个隐式转换,它将任何具有Ordering类型类实例的值转换为一个Ordered值(请参阅Ordered.orderingToOrdered),因此我们没有什么特别的事情可以自动将 的任何实例传递Foo给期望一个 的函数Ordered[Foo]


更新:关于你的新问题:

稍微相关 - 有没有办法组成订单?

一种方法是使用基于Ordering.by元组并转换为元组的大部分相同技术,但显式传递排序以组合:

val byXOrdering = Ordering.by{ foo: Foo => foo.x }
val byYOrdering = Ordering.by{ foo: Foo => foo.y }
val byZOrdering = Ordering.by{ foo: Foo => foo.z }

// Compose byXOrdering and byYOrdering:
val byXThenYOrdering = Ordering.by{ foo: Foo => (foo, foo) }(Ordering.Tuple2(byXOrdering, byYOrdering))

// Compose byXOrdering and byYOrdering and byZOrdering:
val byXThenYThenZOrdering = Ordering.by{ foo: Foo => (foo, foo, foo) }(Ordering.Tuple3(byXOrdering, byYOrdering, byZOrdering))

但它相对“嘈杂”。仅使用标准库我找不到更好的东西,所以我实际上建议使用我们自己的助手:

final class CompositeOrdering[T]( val ord1: Ordering[T], val ord2: Ordering[T] ) extends Ordering[T] {
  def compare( x: T, y: T ) = {
    val comp = ord1.compare( x, y )
    if ( comp != 0 ) comp else ord2.compare( x, y )
  }
}
object CompositeOrdering {
  def apply[T]( orderings: Ordering[T] * ) = orderings reduceLeft (_ orElse _)
}
implicit class OrderingOps[T]( val ord: Ordering[T] ) extends AnyVal {
  def orElse( ord2: Ordering[T] ) = new CompositeOrdering[T]( ord, ord2 )
}

可以这样使用:

val byXOrdering = Ordering.by{ foo: Foo => foo.x }
val byYOrdering = Ordering.by{ foo: Foo => foo.y }
val byZOrdering = Ordering.by{ foo: Foo => foo.z }

// Compose byXOrdering and byYOrdering:
val byXThenYOrdering = byXOrdering orElse byYOrdering

// Compose byXOrdering and byYOrdering and byZOrdering:
val byXThenYThenZOrdering = byXOrdering orElse byYOrdering orElse byZOrdering

或者更简单,像这样:

// Compose byXOrdering and byYOrdering:
val byXThenYOrdering = CompositeOrdering(byXOrdering, byYOrdering)

// Compose byXOrdering and byYOrdering and byZOrdering:
val byXThenYThenZOrdering = CompositeOrdering(byXOrdering, byYOrdering, byZOrdering)

CompositeOrdering.apply基本上就是你Ordering.multipleBy在问题中所说的。

于 2013-02-04T21:58:13.483 回答
5

如果你想要最大的速度——不是你要求的,我知道!——并且仍然清晰,你可以

def compare(that: Foo): Int = {
  this.length compareTo that.length match { case 0 =>; case c => return c }
  this.x      compareTo that.x      match { case 0 =>; case c => return c }
  this.y.size compareTo that.y.size match { case 0 =>; case c => return c }
  this.z.head compareTo that.z.head match { case 0 =>; case c => return c }
  0
}

还有各种不错的基于集合的解决方案和其他解决方案,我将留给其他人解释。(注意所有的样板文件,并观察到你真正需要知道的只是_.length在每种情况下......这会激发 a compareBy,例如。)

于 2013-02-04T21:37:39.837 回答
3

我能想到的最好的是:

def compare(that: Foo) = multiCompare(
  this.length compareTo that.length      // primary comparison
  this.x      compareTo that.x,          // secondary comparison
  this.y.size compareTo that.y.size,     // tertiary comparison
  this.z.head compareTo that.z.head,     // final tie breaker
)

def multiCompare(c: ( => Int)*) = c find {_ != 0} getOrElse 0
于 2013-02-04T21:48:56.813 回答
0

避免OrderedOrdering如果可以的话。按多个条件排序的最简单方法是使用sortBy. 这是一个例子:

val foos: Seq[Foo] = ???
foos.sortBy(f => (f.x, f.y, f.z, ...))

如果你真的想实现Ordered我建议你看看https://stackoverflow.com/a/19348339/1262728

如果你真的想要一个Ordering,最简单的写法如下:

val fooOrdering: Ordering[Foo] = Ordering.by(f => (f.x, f.y, f.z))
于 2022-01-26T13:36:46.770 回答
-1

我使用这种语法是因为它是自记录的:

  val compositeOrdering: Ordering[Foo] =
    Comparator.comparing[Foo, CustomType](_.x, new ExplicitOrderingForCustomType)
    .thenComparingDouble(_.y)
    .thenComparingInt(_.z)
    .compare

它正在构建一个复合材料java.util.Comparator,然后将其compare方法转换为scala.math.Ordering

于 2021-02-11T05:59:30.053 回答