tutorial - Idioma Scala para ordenar por mĂșltiples criterios
scala metodo (3)
Quiero hacer algo como esto:
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
}
}
Me preguntaba si había alguna forma más limpia de escribir este tipo de cosas. Estoy esperando algo como Ordering.multipleBy(ordering: Ordered[A]*)
que toma una variedad de comparables y selecciona el primero que no es cero.
A menudo es mejor usar Ordering
lugar de Ordered
. Ordering
es una clase de tipo y es mucho más flexible que la Ordered
(aunque solo sea porque la orden debe ser implementada por el tipo que se compara, mientras que con la Ordering
se puede definir afuera). Para definir el orden natural (instancia de orden predeterminado) para su tipo, simplemente defina un valor de orden implícito en el objeto complementario.
Entonces, basta con el preámbulo. Lo bueno es que cuando se utiliza la Ordering
lo que se quiere hacer es bastante simple, ya que existe una ordenación implícita para las tuplas (siempre que los elementos de la tupla tengan una ordenación) `:
object Foo {
implicit val FooOrdering = Ordering.by{ foo: Foo =>
(foo.length, foo.x, foo.y, foo.z)
}
}
Además, hay una conversión implícita que convierte cualquier valor que tenga una instancia de clase de tipo de Ordering
en un valor Ordered
(ver Ordered
), por lo que no tenemos nada especial que hacer para poder pasar Ordered.orderingToOrdered
cualquier instancia de Foo
a una función que espera un Ordered[Foo]
ACTUALIZACIÓN : Con respecto a su nueva pregunta:
Un poco relacionado: ¿hay alguna forma de componer los pedidos?
Una forma de hacerlo sería utilizar principalmente la misma técnica basada en Ordering.by
y conversión a tuplas, pero pasando explícitamente los pedidos para componer:
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))
Pero es relativamente "ruidoso". No pude encontrar nada mejor usando solo la biblioteca estándar, por lo que aconsejaría usar nuestro propio ayudante:
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 )
}
Que se puede usar así:
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
O incluso más simple, así:
// Compose byXOrdering and byYOrdering:
val byXThenYOrdering = CompositeOrdering(byXOrdering, byYOrdering)
// Compose byXOrdering and byYOrdering and byZOrdering:
val byXThenYThenZOrdering = CompositeOrdering(byXOrdering, byYOrdering, byZOrdering)
CompositeOrdering.apply
es básicamente lo que llamó Ordering.multipleBy
en su pregunta.
Lo mejor que puedo pensar es esto:
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
Si quieres la velocidad máxima, no lo que pediste, ¡lo sé! Y aún así una claridad decente, puedes
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
}
Hay varias soluciones agradables basadas en colecciones y otras también, que dejaré que otros expliquen. (Tenga en cuenta todo el contenido y observe que todo lo que necesita saber es _.length
en cada caso ... lo que motiva una compareBy
, por ejemplo).