Объяснение Kotlin groupBy() и partition()
В этой статье мы рассмотрим две полезные функции в Kotlin — GroupBy() и Partition(). Мы увидим, как их можно применить в нашем коде и в каких случаях они могут помочь.
Подготовка кода
Но прежде чем мы приступим к рассмотрению, давайте реализуем необходимый код для работы.
Добавим класс Item.kt:
data class Item( val name: String, val type: ItemType ) enum class ItemType { STANDARD, PREMIUM, OTHER }
Далее давайте реализуем простой список, заполненный экземплярами Item:
val items = listOf( Item(name = "Item #1", type = ItemType.STANDARD), Item(name = "Item #2", type = ItemType.OTHER), Item(name = "Item #3", type = ItemType.PREMIUM), Item(name = "Item #4", type = ItemType.STANDARD), Item(name = "Item #5", type = ItemType.PREMIUM) )
Как мы видим, это простой список, доступный только для чтения и содержащий различные элементы. В реальных сценариях это может быть список, полученный из базы данных или другого внешнего источника.
groupBy()
После всего этого давайте начнем с первой функции сегодняшнего сравнения Kotlin groupBy() и Partition().
Как следует из названия, эта функция позволяет нам группировать элементы массива по ключу, возвращаемому keySelector (который нам нужно передать). В результате он возвращает карту, где каждому ключу сопоставлена карта значений из сопоставленного массива.
Простая группаBy()
Чтобы лучше понять это, давайте посмотрим на первый пример:
val groupedByItems = items.groupBy { it.type } println(groupedByItems)
Если мы запустим его, мы должны увидеть следующий вывод (отформатированный вручную для удобства чтения):
{ STANDARD=[Item(name=Item #1, type=STANDARD), Item(name=Item #4, type=STANDARD)], OTHER=[Item(name=Item #2, type=OTHER)], PREMIUM=[Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)] }
Мы ясно видим, что наш список был преобразован в Map<ItemType, List<Item>>.
Кроме того, мы можем использовать ссылки на свойства Kotlin , чтобы сделать наш код еще более понятным:
val groupedByItemsReference = items.groupBy(Item::type) println(groupedByItemsReference)
И результат остается точно таким же:
{ STANDARD=[Item(name=Item #1, type=STANDARD), Item(name=Item #4, type=STANDARD)], OTHER=[Item(name=Item #2, type=OTHER)], PREMIUM=[Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)] }
Преобразование значений
В качестве следующего примера представим, что нам нужно получить списки имен для каждого типа.
Для этого мы можем использовать другую версию функции groupBy() :
val groupedByItemsNamesOnly = items.groupBy( (Item::type), (Item::name) ) println(groupedByItemsNamesOnly)
Далее давайте проверим наш пример:
{ STANDARD=[Item #1, Item #4], OTHER=[Item #2], PREMIUM=[Item #3, Item #5] }
Мы ясно видим, что на этот раз возвращается Map<ItemType, List<String>>.
Конечно, мы могли бы вручную сопоставить элементы из предыдущего примера, чтобы добиться того же результата, но такой подход помогает нам сделать наш код действительно аккуратным и кратким.
Разделить список
Напротив, давайте посмотрим на пример, объясняющий, как не следует использовать groupBy() .
Допустим, мы хотели бы разделить наш список на две части:
- первый - содержит только PREMIUM items
- а второй - с другими внутри
Давайте посмотрим, как эту задачу можно выполнить с помощью groupBy():
val premiumItems = items.groupBy(Item::type)[ItemType.PREMIUM] val otherItems = items.groupBy(Item::type) .filterNot { it.key == ItemType.PREMIUM } .values .flatten() println("Premium: $premiumItems") println("Other types: $otherItems")
В результате мы должны получить следующий вывод:
Premium: [Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)] Other types: [Item(name=Item #1, type=STANDARD), Item(name=Item #4, type=STANDARD), Item(name=Item #2, type=OTHER)]
В принципе, мы добились того, чего хотели. Тем не менее, это решение действительно неаккуратное, и другим людям может быть сложно понять наш код.
Конечно, его можно было бы просто привести к такому виду:
val premiumTypesFiltered = items.filter { it.type == ItemType.PREMIUM } val otherTypesFiltered = items.filterNot { it.type == ItemType.PREMIUM } println("Premium: $premiumTypesFiltered") println("Other types: $otherTypesFiltered")
Тем не менее, в следующей главе мы увидим, что можем сделать этот код еще лучше.
partition()
С учетом вышесказанного, давайте посмотрим, что такое функция partition() в Котлине . Согласно документации:
Разбивает исходный массив на пару списков, где первый список содержит элементы, для которых предикат выдалtrue
, а второй список содержит элементы, для которых предикат выдалfalse
.
Проще говоря, от нас требуется передать предикат (логическое выражение) и в результате мы получаем Пара, содержащая два списка:
- первый, с элементами, оцененными как true
- и второй - то же самое, но с false
Чтобы лучше понять это, давайте посмотрим на его реализацию:
public inline fun <T> Iterable<T>.partition( predicate: (T) -> Boolean ): Pair<List<T>, List<T>> { val first = ArrayList<T>() val second = ArrayList<T>() for (element in this) { if (predicate(element)) { first.add(element) } else { second.add(element) } } return Pair(first, second) }
Мы ясно видим, что элементы, получившие значение true, добавляются в первый ArrayList, тогда как элементы, получившие значение false, попадают во второй . Наконец, оба они объединяются в новый экземпляр Pair.
Simple partition()
Учитывая все вышесказанное, давайте посмотрим, чем нам может помочь partition().
Повторим пример разделения с уже имеющимися знаниями:
val partitioned = items.partition { it.type == ItemType.PREMIUM } println("Premium: ${partitioned.first}") println("Other types: ${partitioned.second}")
Далее давайте запустим приведенный выше код:
Premium: [Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)] Other types: [Item(name=Item #1, type=STANDARD), Item(name=Item #2, type=OTHER), Item(name=Item #4, type=STANDARD)]
Как мы видим, благодаря partition() мы смогли значительно сократить объём кода, чтобы добиться того же результата.
partition() и объявления деструктуризации
Тем не менее, давайте посмотрим, как мы можем еще больше улучшить наш код, объединив partition() с объявлениями деструктуризации.
Проще говоря, объявление деструктуризации создает сразу несколько переменных.
Давайте проверим приведенный ниже код, чтобы увидеть его на практике:
val (premiumTypes, otherTypes) = items.partition { it.type == ItemType.PREMIUM } println("Premium: $premiumTypes") println("Other types: $otherTypes")
Аналогично, давайте запустим пример:
Premium: [Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)] Other types: [Item(name=Item #1, type=STANDARD), Item(name=Item #2, type=OTHER), Item(name=Item #4, type=STANDARD)]
Как мы видим, благодаря комбинации partition() и объявлений деструктуризации мы создали две новые переменные: premiumTypes иotherTypes.
По сравнению с предыдущим примером это эквивалентно:
val premiumTypes = partitioned.first val otherTypes = partitioned.second
5. Kotlin groupBy() и partition() Резюме
И это все, что касается этой статьи, посвященной функциям Kotlin groupBy() и Partition(). Спасибо за прочтение.