Скажем, у меня есть class Foo(val a: String, val b: Int, val c: Date)
и я хочу отсортировать список на Foo
основе всех трех свойств. Как мне это сделать?
Скажем, у меня есть class Foo(val a: String, val b: Int, val c: Date)
и я хочу отсортировать список на Foo
основе всех трех свойств. Как мне это сделать?
Ответы:
Stdlib Kotlin предлагает для этого ряд полезных вспомогательных методов.
Во-первых, вы можете определить компаратор с помощью 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})