根据 Kotlin 中的条件将列表拆分为连续元素组

Hus*_*ain 6 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
}
Run Code Online (Sandbox Code Playgroud)

现在这个乐趣将需要

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")
)
Run Code Online (Sandbox Code Playgroud)

并返回

[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)]
Run Code Online (Sandbox Code Playgroud)

这按预期工作。因为我正在尝试学习 Kotlin,并且我知道有一种很好的方法可以做到这一点,所以我想知道如何以 kotlin 的方式简化这个逻辑。

Fur*_*ish 1

这是原始答案和原始解决方案。为了更好的(imo)实现,滚动到编辑

不同的语言选择不同的方法来实现分组功能

某些语言(例如 Kotlin)采用的实现方法groupBy是采用一元函数 (T) -> U并返回一个字典,该字典将 every 映射到映射到它的 sU列表。T其他语言(例如 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
}
Run Code Online (Sandbox Code Playgroud)

并这样称呼它:

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)
}
Run Code Online (Sandbox Code Playgroud)

这会产生:

[[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)]]
Run Code Online (Sandbox Code Playgroud)

我们在这里使用任何一个扩展Iterable方法T。这是在 Kotlin 中引入此类功能的惯用方式,因为这是任何Iterable. 我还将其设为通用 ( T) 并接受任何predicate将使用连续元素进行测试的内容。


编辑:实际上,有一种功能可以将每个元素逐一累积到某种结构。它有时被称为“accumulate”“reduce”,或者在 Kotlin 中称为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
        }
Run Code Online (Sandbox Code Playgroud)

这是一个单一的表达方式,可以说比前一个表达方式更惯用。它不使用原始循环,并且在代码部分之间不保留任何状态。它也非常简单 - 要么Iterable有问题的是空的,在这种情况下,我们返回一个空列表,或者,如果存在元素,我们将它们折叠成一个组列表(Lists List)。

请注意drop(1)- 我们这样做是因为我们将fold所有元素放入已包含该元素的最终列表中(通过调用中的构造fold())。通过这样做,我们可以避免引入额外的检查列表是否为空的过程。