【问题标题】:Split a list into groups of consecutive elements based on a condition in Kotlin根据 Kotlin 中的条件将列表拆分为连续元素组
【发布时间】:2021-10-18 08:35:25
【问题描述】:

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

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 的方式简化这个逻辑。

【问题讨论】:

  • 使用类型为键、ArrayList为值的HashMap。
  • 请问您的用例是什么?通常我们会使用groupBy 或类似的实用程序,但它不会在结果中保留“序列”信息。所以我想知道为什么你需要这个数据结构作为输出。
  • 其实物品是按时间排序的,我需要保留顺序
  • 另一种方法可能是使用fold()。 (抱歉,现在没时间写东西;之前的问题中可能有示例。  或者它会成为一个有启发性的练习 :-)

标签: kotlin


【解决方案1】:

这是原始解决方案的原始答案。要获得更好的 (imo) 实施,请滚动到 EDIT

Different approaches are chosen for the grouping functionality by different languages.

一些语言,如 Kotlin,采用实现 groupBy 的方法,采用一元 函数 (T) -&gt; U 并返回一个字典,该字典将每个 U 映射到 Ts 列表映射到它。其他语言,如 Haskell,使用 (T, T) -&gt; 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)]]

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


编辑:实际上,有一种功能可以将每个元素一个一个地累积到某个结构中。它有时被称为 accumulatereduce 或在 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)。

注意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

      【讨论】:

      • 这只是为了一分为二。
      猜你喜欢
      • 1970-01-01
      • 2021-07-24
      • 2019-09-18
      • 1970-01-01
      • 2019-10-12
      • 1970-01-01
      • 1970-01-01
      • 2017-07-30
      相关资源
      最近更新 更多