¿Cómo ordenar según / comparar múltiples valores en Kotlin?

84

Digamos que tengo un class Foo(val a: String, val b: Int, val c: Date)y quiero ordenar una lista de Foocorreos electrónicos según las tres propiedades. ¿Cómo haría esto?

Kirill Rakhman
fuente

Respuestas:

146

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 Fooimplemente 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()

Dirección de clasificación

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 })

Consideraciones de rendimiento

La varargversión de compareValuesByno 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)
    }
}
Kirill Rakhman
fuente
4
Tenga en cuenta que si está utilizando varias funciones lambdas (hay una sobrecarga con exactamente una que está en línea), no están en línea . Lo que significa que cada llamada a comapreTo crea nuevos objetos. Para evitarlo, puede mover los selectores al objeto complementario para que los selectores solo se asignen una vez. Creé un recorte
Paul Woitaschek
1
@KirillRakhman Crea singletons para las funciones pero aún asigna matrices:ANEWARRAY kotlin/jvm/functions/Function1
Paul Woitaschek
1
Comenzar con Kotlin 1.1.3 compareBycon múltiples lambdas no asignará una nueva matriz en cada compareTollamada.
Ilya
1
@Ilya, ¿podría indicarme el registro de cambios relevante u otra información para este tipo de optimización?
Kirill Rakhman
0

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 })).

CoolMind
fuente
0

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})
Yago Rey Viñas
fuente
1
Esto está cubierto en mi respuesta.
Kirill Rakhman