Powiedzmy, że mam class Foo(val a: String, val b: Int, val c: Date)
i chcę posortować listę Foo
s na podstawie wszystkich trzech właściwości. Jak bym to zrobił?
Powiedzmy, że mam class Foo(val a: String, val b: Int, val c: Date)
i chcę posortować listę Foo
s na podstawie wszystkich trzech właściwości. Jak bym to zrobił?
Odpowiedzi:
Stdlib Kotlina oferuje do tego szereg przydatnych metod pomocniczych.
Najpierw możesz zdefiniować komparator za pomocą compareBy()
metody i przekazać go do sortedWith()
metody rozszerzającej, aby otrzymać posortowaną kopię listy:
val list: List<Foo> = ...
val sortedList = list.sortedWith(compareBy({ it.a }, { it.b }, { it.c }))
Po drugie, możesz pozwolić Foo
implementacji Comparable<Foo>
za pomocą compareValuesBy()
metody pomocniczej:
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 })
}
Następnie możesz wywołać sorted()
metodę rozszerzenia bez parametrów, aby otrzymać posortowaną kopię listy:
val sortedList = list.sorted()
Jeśli potrzebujesz sortować rosnąco według niektórych wartości i malejąco według innych wartości, standardowa biblioteka oferuje również funkcje do tego:
list.sortedWith(compareBy<Foo> { it.a }.thenByDescending { it.b }.thenBy { it.c })
vararg
Wersja compareValuesBy
nie jest wstawiane w kodu bajtowego oznaczającego anonimowe klas zostanie wygenerowany dla lambda. Jeśli jednak same lambdy nie przechwytują stanu, będą używane pojedyncze wystąpienia zamiast tworzenia wystąpienia lambd za każdym razem.
Jak zauważył Paul Woitaschek w komentarzach, porównanie z wieloma selektorami spowoduje za każdym razem utworzenie instancji tablicy dla wywołania vararg. Nie możesz tego zoptymalizować, wyodrębniając tablicę, ponieważ będzie ona kopiowana przy każdym wywołaniu. Z drugiej strony możesz wyodrębnić logikę do statycznej instancji komparatora i użyć jej ponownie:
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
z wieloma lambdami nie będzie alokować nowej tablicy przy każdym compareTo
wywołaniu.
Jeśli chcesz posortować w kolejności malejącej, możesz użyć zaakceptowanej odpowiedzi:
list.sortedWith(compareByDescending<Foo> { it.a }.thenByDescending { it.b }.thenByDescending { it.c })
Lub utwórz funkcję rozszerzenia, taką jak 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
}
i użyć: list.sortedWith(compareByDescending ({ it.a }, { it.b }, { it.c }))
.
Jeśli potrzebujesz sortować według wielu pól, a niektóre pola malejąco, a inne rosnąco, możesz użyć:
YOUR_MUTABLE_LIST.sortedWith(compareBy<YOUR_OBJECT> { it.PARAM_1}.thenByDescending { it.PARAM_2}.thenBy { it.PARAM_3})