#list #kotlin
Вопрос:
Я пытаюсь сгруппировать список по какому — то типу и если они расположены последовательно
data class Item(val type: Int, val name: String)
private fun splitItems(items: List<Item>): List<List<Item>> {
val groupedItems = mutableListOf<List<Item>>()
var tempList = mutableListOf<Item>()
items.mapIndexed { index, item ->
if (index > 0) {
val previousItem = items[index - 1]
if (previousItem.type == item.type) {
tempList.add(item)
} else {
if (tempList.isNotEmpty()) groupedItems.add(tempList)
tempList = mutableListOf()
tempList.add(item)
}
} else tempList.add(item)
}
if (tempList.isNotEmpty()) groupedItems.add(tempList)
return groupedItems
}
Теперь это развлечение займет
val items = mutableListOf(
Item(1, "Shirt"),
Item(1, "Shirt"),
Item(2, "Pant"),
Item(2, "Pant"),
Item(2, "Pant"),
Item(1, "Shirt"),
Item(1, "Shirt"),
Item(3, "Tee"),
Item(3, "Tee"),
Item(2, "Pant"),
Item(2, "Pant"),
Item(1, "Shirt"),
Item(1, "Shirt"),
Item(1, "Shirt")
)
и вернуться
[Item(type=1, name=Shirt), Item(type=1, name=Shirt)]
[Item(type=2, name=Pant), Item(type=2, name=Pant), Item(type=2, name=Pant)]
[Item(type=1, name=Shirt), Item(type=1, name=Shirt)]
[Item(type=3, name=Tee), Item(type=3, name=Tee)]
[Item(type=2, name=Pant), Item(type=2, name=Pant)]
[Item(type=1, name=Shirt), Item(type=1, name=Shirt), Item(type=1, name=Shirt)]
Это работает так, как и ожидалось. Поскольку я пытаюсь изучить Котлин и знаю, что есть прекрасный способ сделать это, я хотел бы знать, как я могу упростить эту логику способом котлина.
Комментарии:
1. Используйте хэш-карту с типом в качестве ключа и ArrayList<Item> в качестве значения.
2. Могу я спросить, каков ваш вариант использования для этого? Обычно мы использовали
groupBy
бы или аналогичные утилиты, но это не сохранит информацию о «последовательности» в результате. Поэтому мне интересно, зачем вам нужна эта структура данных в качестве вывода.3. На самом деле элементы сортируются по времени, и мне нужно сохранить последовательность
4. Можно было бы использовать другой подход
fold()
. (Извините, сейчас нет времени что-то писать; вероятно, в предыдущих вопросах есть примеры. Или это было бы поучительным упражнением 🙂
Ответ №1:
Это оригинальный ответ с оригинальным решением. Для лучшей реализации (imo) перейдите к РЕДАКТИРОВАНИЮ
Для функций группировки по разным языкам выбраны разные подходы.
Некоторые языки, такие как Kotlin, используют подход реализации groupBy
, используя унарную функцию (T) -> U
и возвращая словарь, который сопоставляет каждый U
со списком T
s, сопоставленных с ним. Другие языки, такие как Haskell, работают с (T, T) -> Boolean
предикатами, которые группируют последовательные элементы, удовлетворяющие предикату.
Никакая функциональность в Kotlin не поддерживает удобно такую операцию, которую вы хотите использовать. Из-за этого вам придется реализовывать свои собственные. Немного более короткий код, чем у вас, был бы:
fun <T> Iterable<T>.groupConsecutiveBy(predicate: (T, T) -> Boolean): List<List<T>> {
var leftToGroup = this.toList()
val groups = mutableListOf<List<T>>()
while (leftToGroup.isNotEmpty()) {
val firstItem = leftToGroup[0]
val newGroup = leftToGroup.takeWhile { predicate(it, firstItem) }
groups.add(newGroup)
leftToGroup = leftToGroup.subList(newGroup.size, leftToGroup.size)
}
return groups
}
и назови это так:
fun main() {
val items = mutableListOf(
Item(1, "Shirt"),
Item(1, "Shirt"),
Item(2, "Pant"),
Item(2, "Pant"),
Item(2, "Pant"),
Item(1, "Shirt"),
Item(1, "Shirt"),
Item(3, "Tee"),
Item(3, "Tee"),
Item(2, "Pant"),
Item(2, "Pant"),
Item(1, "Shirt"),
Item(1, "Shirt"),
Item(1, "Shirt")
)
items.groupConsecutiveBy{ left, right -> left.type == right.type }.also(::print)
}
что дало бы:
[[Item(type=1, name=Shirt), Item(type=1, name=Shirt)], [Item(type=2, name=Pant), Item(type=2, name=Pant), Item(type=2, name=Pant)], [Item(type=1, name=Shirt), Item(type=1, name=Shirt)], [Item(type=3, name=Tee), Item(type=3, name=Tee)], [Item(type=2, name=Pant), Item(type=2, name=Pant)], [Item(type=1, name=Shirt), Item(type=1, name=Shirt), Item(type=1, name=Shirt)]]
Мы используем метод расширения na для Iterable
любого T
здесь. Это идиоматический способ внедрения такой функциональности в Kotlin, потому что это операция, которую выполнит любой Iterable
. Я также сделал его универсальным ( T
) и принял все predicate
, что будет проверено с помощью последовательных элементов.
ПРАВКА: На самом деле, существует функциональность, которая накапливает каждый отдельный элемент один за другим в определенной структуре. Это иногда называют накоплением, уменьшением или, в Котлине, fold
:
fun <T> Iterable<T>.groupConsecutiveBy(groupIdentifier: (T, T) -> Boolean) =
if (!this.any())
emptyList()
else this
.drop(1)
.fold(mutableListOf(mutableListOf(this.first()))) { groups, t ->
groups.last().apply {
if (groupIdentifier.invoke(last(), t)) {
add(t)
} else {
groups.add(mutableListOf(t))
}
}
groups
}
Это единственное выражение, которое, возможно, даже более идиоматично, чем предыдущее. Он не использует необработанные циклы и не содержит состояния между частями кода. Это также очень просто — либо Iterable
рассматриваемый объект пуст, и в этом случае мы возвращаем пустой список, либо, в случае наличия элементов, мы складываем их в список групп ( List
групп List
).
Обратите drop(1)
внимание — мы делаем это, потому что мы fold
все элементы в конечном списке, который уже содержит этот элемент (по конструкции в fold()
вызове). Поступая таким образом, мы избавляем себя от введения дополнительных проверок на пустоту списка.
Ответ №2:
Вот краткий и эффективный способ сделать это с помощью императивного кода. Я не могу придумать чисто функциональное решение из стандартной библиотеки, которое было бы столь же эффективным. На мой взгляд, использование fold
, а затем внутреннее управление несколькими списками и обработка первого элемента как особого случая не так удобочитаемы и не так эффективны.
fun <T, C> Iterable<T>.chunkedByConsecutive(comparisonSelector: (T) -> C): List<List<T>> {
if (none()) return emptyList()
val lists = mutableListOf<MutableList<T>>()
var previous: C? = null
for (item in this) {
if (previous != comparisonSelector(item).also { previous = it } || lists.isEmpty()) {
lists = mutableListOf(item)
} else {
lists.last() = item
}
}
return lists
}
Ответ №3:
Вы должны использовать раздел.
val array = intArrayOf(1, 2, 3, 4, 5)
val (even, odd) = array.partition { it % 2 == 0 }
println(even) // [2, 4]
println(odd) // [1, 3, 5]
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/partition.html
Комментарии:
1. Это просто для разделения на две части.