Подтвердить что ты не робот

Scala идиома для заказа по нескольким критериям

Я хочу сделать что-то вроде этого:

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]*), которая берет varargs сравниваемых и выбирает сначала ненулевое значение.

4b9b3361

Ответ 1

Часто лучше использовать 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 в своем вопросе.

Ответ 2

Если вы хотите максимальную скорость - не то, что вы просили, я знаю! - и по-прежнему достойная ясность, вы можете

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 в каждом случае... например, мотивирует compareBy.)

Ответ 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