null

Комплексная сортировка в Kotlin: Смешанный порядок сортировки

Представьте, что у вас есть список идентификационных номеров или номеров продуктов в формате "A-123", где каждый идентификатор состоит из буквы и цифры. Теперь предположим, что вам нужно отсортировать эти идентификаторы таким образом, чтобы алфавитная часть была упорядочена по возрастанию, а числовая - по убыванию. Как бы вы решили эту задачу на языке Kotlin?

В этой статье мы рассмотрим, как добиться желаемых результатов сортировки, используя эффективные возможности языка Kotlin.

 

Оригинал статьи: Advanced Sorting Techniques in Kotlin: Mixed Ascending and Descending Order by Michihiro Iwasaki

 

Для начала рассмотрим подход, который не дает ожидаемого результата сортировки.

Предположим, что исходно у нас имеется следующий список:

val itemList = listOf(
    "A-123",
    "B-032",
    "A-025",
    "C-112",
    "C-054",
    "A-372",
    "B-004",
    "C-010",
    "B-538"
)

И нам нужно отсортировать этот список следующим образом:

1) Отсортировать алфавитную часть по возрастанию.

2) Отсортировать числовую часть по убыванию.

val expectedList = listOf(
    "A-372",
    "A-123",
    "A-025",
    "B-538",
    "B-032",
    "B-004",
    "C-112",
    "C-054",
    "C-010"
)

Сортировка только по возрастанию или убыванию не представляет сложности. Например, сортировка по первому символу (алфавиту) в порядке возрастания может быть выполнена следующим образом:

fun main() {
    val sortedList = itemList.sortedBy {
        it.first()
    }
    
    for (item in sortedList) {
        println(item)
    }
    /**
     * Вывод:
     * 
     * A-123
     * A-025
     * A-372
     * B-032
     * B-004
     * B-538
     * C-112
     * C-054
     * C-010
     * 
     */
}

Функция sortedBy правильно сортирует алфавитную часть. Однако ей не удается отсортировать числовую часть в порядке убывания. Попытка добавить сортировку по убыванию для чисел приводит к неожиданному результату:

fun main() {
    val sortedList = itemList.sortedBy {
        it.first()
    }.sortedByDescending {
        it.slice(2..4)
    }
    
    for (item in sortedList) {
        println(item)
    }
    /**
     * Вывод:
     * 
     * B-538
     * A-372
     * A-123
     * C-112
     * C-054
     * B-032
     * A-025
     * C-010
     * B-004
     * 
     */
}

Эта проблема возникает потому, что вторая операция сортировки отменяет первую, нарушая первоначальный алфавитный порядок сортировки.

 

Давайте теперь рассмотрим подход, который успешно достигает нужного нам результата.

Чтобы одновременно применить условия сортировки по возрастанию и убыванию, можно использовать функцию sortedWith(). В этой функции для первичного условия сортировки используется compareBy() или compareByDescending(), а для вторичного - thenBy() или thenByDescending().

Вот пример кода, который позволяет достичь желаемого результата:

val itemList = listOf(
    "A-123",
    "B-032",
    "A-025",
    "C-112",
    "C-054",
    "A-372",
    "B-004",
    "C-010",
    "B-538"
)

val expectedList = listOf(
    "A-372",
    "A-123",
    "A-025",
    "B-538",
    "B-032",
    "B-004",
    "C-112",
    "C-054",
    "C-010"
)

fun main() {
    val sortedList = itemList.sortedWith(
        compareBy<String> {
            it.first()
        }.thenByDescending {
            it.slice(2..4)
        }
    )
    
    for (item in sortedList) {
        println(item)
    }
    /**
     * Вывод:
     * 
     * A-372
     * A-123
     * A-025
     * B-538
     * B-032
     * B-004
     * C-112
     * C-054
     * C-010
     * 
     */
     
     println(expectedList == sortedList) // Вывод: true
}

Отлично! Мы успешно получили ожидаемый результат.

Однако важно явно указывать тип в compareBy() или compareByDescending(). Невыполнение этого требования может привести к ошибке.

Например:

val sortedList = itemList.sortedWith(
    compareBy { // Этот код приведет к ошибке
        it.first()
    }.thenByDescending {
        it.slice(2..4)
    }
)

 

Давайте теперь рассмотрим как реализовать аналогичную сортировку для списка объектов.

Подход к применению нескольких условий сортировки к списку объектов данных аналогичен тому, что используется при работе с обычными списками.

Рассмотрим пример класса данных и соответствующего списка:

data class Item (
    val group: String,
    val num: Int
)

val itemList = listOf(
    Item("A", 123),
    Item("B", 32),
    Item("A", 25),
    Item("C", 112),
    Item("C", 54),
    Item("A", 372),
    Item("B", 4),
    Item("C", 10),
    Item("B", 538),
)

На этот раз отсортируем список в порядке убывания по алфавиту (group) и в порядке возрастания по числам (num). Для алфавитной части используем compareByDescending(), а для числовой части - thenBy().

Ниже приведен код и результат выполнения этой сортировки:


fun main() {
    val sortedList = itemList.sortedWith(
        compareByDescending<Item> {
            it.group
        }.thenBy {
            it.num
        }
    )
    
    for (item in sortedList) {
        println("${item.group}: ${item.num}")
    }
    
    /**
     * Вывод:
     * 
     * C: 10
     * C: 54
     * C: 112
     * B: 4
     * B: 32
     * B: 538
     * A: 25
     * A: 123
     * A: 372
     */
     
}

Превосходно! Мы успешно справились и со списком объектов.

 

Конец статьи.