Скажімо, у мене є class Foo(val a: String, val b: Int, val c: Date)
і я хочу відсортувати список Foo
s на основі всіх трьох властивостей. Як би я про це пішов?
Скажімо, у мене є class Foo(val a: String, val b: Int, val c: Date)
і я хочу відсортувати список Foo
s на основі всіх трьох властивостей. Як би я про це пішов?
Відповіді:
Stdlib Котліна пропонує ряд корисних допоміжних методів для цього.
По-перше, ви можете визначити порівняльник за допомогою compareBy()
методу і передати його sortedWith()
методу розширення, щоб отримати відсортовану копію списку:
val list: List<Foo> = ...
val sortedList = list.sortedWith(compareBy({ it.a }, { it.b }, { it.c }))
По-друге, ви можете дозволити Foo
реалізації, Comparable<Foo>
використовуючи compareValuesBy()
допоміжний метод:
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 })
}
Тоді ви можете викликати sorted()
метод розширення без параметрів, щоб отримати відсортовану копію списку:
val sortedList = list.sorted()
Якщо вам потрібно сортувати за зростанням за деякими значеннями та за спаданням за іншими значеннями, stdlib також пропонує функції для цього:
list.sortedWith(compareBy<Foo> { it.a }.thenByDescending { it.b }.thenBy { it.c })
vararg
Версія compareValuesBy
не вбудовуються в байткод означає анонімні класів буде згенеровано для лямбда. Однак, якщо самі лямбди не фіксують стан, замість того, щоб створювати екземпляри лямбд щоразу, будуть використовуватися екземпляри-одиниці.
Як зазначив Пол Войташек у коментарях, порівняння з кількома селекторами створюватиме масив для виклику vararg щоразу. Ви не можете оптимізувати це, витягнувши масив, оскільки він буде скопійований під час кожного дзвінка. З іншого боку, що ви можете зробити, це витягнути логіку в екземпляр статичного компаратора і використати її повторно:
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
з декількома лямбдами, не буде виділятися новий масив під час кожного compareTo
дзвінка.
Якщо ви хочете відсортувати за спаданням, ви можете використати прийняту відповідь:
list.sortedWith(compareByDescending<Foo> { it.a }.thenByDescending { it.b }.thenByDescending { it.c })
Або створіть функцію розширення, наприклад 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
}
і використовувати: list.sortedWith(compareByDescending ({ it.a }, { it.b }, { it.c }))
.
Якщо вам потрібно сортувати за кількома полями, а деякі поля за спаданням, а інші за зростанням, ви можете використовувати:
YOUR_MUTABLE_LIST.sortedWith(compareBy<YOUR_OBJECT> { it.PARAM_1}.thenByDescending { it.PARAM_2}.thenBy { it.PARAM_3})