Сортировка коллекции по нескольким полям в Kotlin
Решение через compareBy и thenBy
В Kotlin для сортировки коллекций по нескольким полям лучше всего использовать функции compareBy и thenBy (или thenByDescending для убывающего порядка). Они создают композитный компаратор, который можно передать в sortedWith.
Вот как решить вашу задачу — сортировка по имени (возрастание), возрасту (возрастание) и росту (убывание):
val sorted = people.sortedWith(
compareBy<Person> { it.name }
.thenBy { it.age }
.thenByDescending { it.height }
)
Объяснение:
compareBy<Person> { it.name }— создаёт первичный компаратор по имени (по умолчанию по возрастанию)..thenBy { it.age }— добавляет вторичную сортировку по возрасту (если имена равны)..thenByDescending { it.height }— добавляет третичную сортировку по росту, но в обратном порядке.
Результат после сортировки:
Person(name=Alice, age=25, height=165.0)
Person(name=Alice, age=30, height=160.0)
Person(name=Bob, age=30, height=180.0)
Person(name=Charlie, age=25, height=170.0)
Обратите внимание: сначала идёт Alice 25 лет (рост 165), затем Alice 30 лет (рост 160) — это потому что возраст сортируется по возрастанию, а рост по убыванию внутри каждой группы с одинаковым именем и возрастом.
Альтернативный синтаксис с лямбдой
Можно использовать более короткую запись без явного указания типа:
val sorted = people.sortedWith(
compareBy({ it.name }, { it.age }, { -it.height.toInt() })
)
Но этот способ менее гибкий: для убывания приходится использовать трюк с отрицанием числа, что не работает со строками или nullable-полями. Лучше придерживаться цепочки thenBy.
Сортировка с nullable-полями
Если поле может быть null, используйте thenBy(nullsLast()) или thenBy(nullsFirst()):
data class Person(val name: String?, val age: Int)
val sorted = people.sortedWith(
compareBy<Person> { it.name }
.thenBy(nullsLast()) { it.age }
)
Сортировка в обратном порядке всей коллекции
Если нужно полностью перевернуть результат, просто добавьте .reversed() в конце:
val sortedDesc = people.sortedWith(
compareBy { it.name }.thenBy { it.age }
).reversed()
Производительность
Все эти методы работают за O(n log n) и используют стабильную сортировку (порядок равных элементов сохраняется). Для больших коллекций (более 10 000 элементов) можно рассмотреть использование sortedBy с цепочкой, но sortedWith обычно предпочтительнее из-за читаемости.
В итоге, ваш код будет выглядеть так:
val sortedPeople = people.sortedWith(
compareBy<Person> { it.name }
.thenBy { it.age }
.thenByDescending { it.height }
)
println(sortedPeople)
<<Использование Kotlin Comparator напрямую
Если вам не нравится синтаксис с цепочкой, можно создать компаратор вручную. Это даёт больше контроля и может быть полезно в сложных случаях, например, когда логика сортировки зависит от внешних условий.
val comparator = Comparator<Person> { a, b ->
val nameCompare = a.name.compareTo(b.name)
if (nameCompare != 0) return@Comparator nameCompare
val ageCompare = a.age.compareTo(b.age)
if (ageCompare != 0) return@Comparator ageCompare
b.height.compareTo(a.height) // убывание — меняем a и b местами
}
val sorted = people.sortedWith(comparator)
Этот подход эквивалентен предыдущему, но более явный. Он удобен, когда нужно добавить отладочный вывод или сложную логику (например, сортировку по последней букве имени или по модулю возраста).
Расширение для списка
Вы также можете написать функцию-расширение для многокритериальной сортировки:
fun <T> List<T>.sortedByMultiple(vararg selectors: Pair<(T) -> Comparable<*>, Boolean>): List<T> {
return this.sortedWith { a, b ->
for ((selector, ascending) in selectors) {
val compareResult = if (ascending) {
compareValuesBy(a, b, selector)
} else {
compareValuesBy(b, a, selector)
}
if (compareResult != 0) return@sortedWith compareResult
}
0
}
}
// Использование:
val sorted = people.sortedByMultiple(
{ it.name } to true,
{ it.age } to true,
{ it.height } to false
)
Этот вариант лаконичен и легко читается, но требует написания дополнительного кода. Выбирайте тот подход, который лучше подходит под ваш стиль и требования проекта.
Ответов пока нет.
Войдите чтобы оставить ответ