根据Kotlin中的条件将列表分为连续元素组 [英] Split a list into groups of consecutive elements based on a condition in Kotlin

查看:176
本文介绍了根据Kotlin中的条件将列表分为连续元素组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试根据某种类型以及它们是否按顺序对列表进行分组

I am trying to group a list based on some type and if they are in sequence

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)]

这按预期工作.由于我正在尝试学习Kotlin,并且我知道这样做的方法很不错,所以我想知道如何以Kotlin的方式简化这种逻辑.

This is working as expected. Since I am trying to learn Kotlin and I know there is a beautiful way of doing this, I would like to know how I can simplify this logic in kotlin way.

推荐答案

这是原始解决方案的原始答案.为了获得更好的(imo)实现,请滚动至EDIT

不同通过不同的语言为分组功能选择了方法.

某些语言,例如Kotlin,采用一元函数 (T) -> U并返回将每个U映射到T列表的字典,从而实现groupBy映射到它.其他语言,例如Haskell,可以与(T, T) -> Boolean 谓词一起使用,这些词将满足谓词的连续元素分组.

Some languages, like Kotlin, take the approach of implementing groupBy by taking a unary function (T) -> U and returning a dictionary that maps every U to a list of Ts that mapped to it. Other languages, like Haskell, work with (T, T) -> Boolean predicates that group consecutive elements that satisfy the predicate.

Kotlin中的任何功能都无法方便地支持您希望使用的这种操作.因此,您必须实现自己的.比您的代码短一点的代码是:

No functionality in Kotlin supports conveniently such operation that you desire to use. Because of that, you have to implement your own. A slightly shorter code than yours would be:

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)]]

我们在这里对任何TIterable使用na扩展方法.这是在Kotlin中引入此类功能的惯用方式,因为它是任何Iterable都可以通过 完成的操作.我也将其设为通用(T),并接受将使用连续元素进行测试的任何predicate.

We are using na extension method for an Iterable of any T here. This is the idiomatic way of introducing such functionality in Kotlin, because it's an operation that will be done by any Iterable. I also made it generic (T) and to accept any predicate that will be tested with consecutive elements.

实际上,有一项功能可以将每个单个元素一个一个地累积到某个结构.有时称为累积减少,或者在科特林语中为 fold :

Actually, there is a functionality that accumulates every single element one by one to a certain structure. It's sometimes called accumulate, reduce or, in 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
        }

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

This is a single expression which is, arguably, even more idiomatic than the previous one. It uses no raw loops and holds no state in between code parts. It's also very simple - either the Iterable in question is empty and, in that case, we return an empty list, or, in case of elements present, we fold them into a list of groups (List of Lists).

请注意drop(1)-之所以这样做,是因为我们将所有元素fold都包含在已经包含该元素的最终列表中(通过fold()调用进行构造).这样一来,我们就可以避免对列表的空性进行其他检查.

Note the drop(1) - we do that because we fold all elements into the final list that already contains that element (by construction in the fold() call). By doing that we save ourselves from introducing additional checks for the emptiness of the list.

这篇关于根据Kotlin中的条件将列表分为连续元素组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆