Digamos que tengo un class Foo(val a: String, val b: Int, val c: Date)
y quiero ordenar una lista de Foo
correos electrónicos según las tres propiedades. ¿Cómo haría esto?
Digamos que tengo un class Foo(val a: String, val b: Int, val c: Date)
y quiero ordenar una lista de Foo
correos electrónicos según las tres propiedades. ¿Cómo haría esto?
Respuestas:
El stdlib de Kotlin ofrece varios métodos de ayuda útiles para esto.
Primero, puede definir un comparador usando el compareBy()
método y pasarlo al sortedWith()
método de extensión para recibir una copia ordenada de la lista:
val list: List<Foo> = ...
val sortedList = list.sortedWith(compareBy({ it.a }, { it.b }, { it.c }))
En segundo lugar, puede dejar que se Foo
implemente Comparable<Foo>
utilizando el compareValuesBy()
método auxiliar:
class Foo(val a: String, val b: Int, val c: Date) : Comparable<Foo> {
override fun compareTo(other: Foo)
= compareValuesBy(this, other, { it.a }, { it.b }, { it.c })
}
Luego puede llamar al sorted()
método de extensión sin parámetros para recibir una copia ordenada de la lista:
val sortedList = list.sorted()
Si necesita ordenar ascendente en algunos valores y descender en otros valores, stdlib también ofrece funciones para eso:
list.sortedWith(compareBy<Foo> { it.a }.thenByDescending { it.b }.thenBy { it.c })
La vararg
versión de compareValuesBy
no está incluida en el código de bytes, lo que significa que se generarán clases anónimas para las lambdas. Sin embargo, si las lambdas mismas no capturan el estado, se usarán instancias singleton en lugar de instanciar las lambdas cada vez.
Como señaló Paul Woitaschek en los comentarios, la comparación con varios selectores creará una instancia de una matriz para la llamada vararg cada vez. No puede optimizar esto extrayendo la matriz, ya que se copiará en cada llamada. Lo que puede hacer, por otro lado, es extraer la lógica en una instancia de comparador estático y reutilizarla:
class Foo(val a: String, val b: Int, val c: Date) : Comparable<Foo> {
override fun compareTo(other: Foo) = comparator.compare(this, other)
companion object {
// using the method reference syntax as an alternative to lambdas
val comparator = compareBy(Foo::a, Foo::b, Foo::c)
}
}
ANEWARRAY kotlin/jvm/functions/Function1
compareBy
con múltiples lambdas no asignará una nueva matriz en cada compareTo
llamada.
Si desea ordenar en orden descendente, puede usar la respuesta aceptada:
list.sortedWith(compareByDescending<Foo> { it.a }.thenByDescending { it.b }.thenByDescending { it.c })
O crea una función de extensión como compareBy
:
/**
* Similar to
* public fun <T> compareBy(vararg selectors: (T) -> Comparable<*>?): Comparator<T>
*
* but in descending order.
*/
public fun <T> compareByDescending(vararg selectors: (T) -> Comparable<*>?): Comparator<T> {
require(selectors.size > 0)
return Comparator { b, a -> compareValuesByImpl(a, b, selectors) }
}
private fun <T> compareValuesByImpl(a: T, b: T, selectors: Array<out (T) -> Comparable<*>?>): Int {
for (fn in selectors) {
val v1 = fn(a)
val v2 = fn(b)
val diff = compareValues(v1, v2)
if (diff != 0) return diff
}
return 0
}
y usar: list.sortedWith(compareByDescending ({ it.a }, { it.b }, { it.c }))
.
Si necesita ordenar por múltiples campos, y algunos campos descendiendo y otros ascendiendo, puede usar:
YOUR_MUTABLE_LIST.sortedWith(compareBy<YOUR_OBJECT> { it.PARAM_1}.thenByDescending { it.PARAM_2}.thenBy { it.PARAM_3})