【问题标题】:Kotlin: Convert large List to sublist of set partition sizeKotlin:将大列表转换为设置分区大小的子列表
【发布时间】:2016-04-02 14:08:05
【问题描述】:

我正在寻找一个等效于Groovy's collate 的函数,它将一个大列表分成多个批次进行处理。我确实看到了subList,它可以适应类似的功能,但想检查并确保我没有错过一个内置的或疯狂的简单替代方法来滚动我自己的。

【问题讨论】:

标签: kotlin


【解决方案1】:

使用 Kotlin 1.3,根据您的需要,您可以选择以下方法之一来解决您的问题。


#1。使用chunked

fun main() {
    val list = listOf(2, 4, 3, 10, 8, 7, 9)
    val newList = list.chunked(2)
    //val newList = list.chunked(size = 2) // also works
    print(newList)
}

/*
prints:
[[2, 4], [3, 10], [8, 7], [9]]
*/

#2。使用windowed

fun main() {
    val list = listOf(2, 4, 3, 10, 8, 7, 9)
    val newList = list.windowed(2, 2, true)
    //val newList = list.windowed(size = 2, step = 2, partialWindows = true) // also works
    println(newList)
}

/*
prints:
[[2, 4], [3, 10], [8, 7], [9]]
*/

【讨论】:

    【解决方案2】:

    注意: 对于 Kotlin 1.2 和更新版本,请参阅标准库中的 chunkedwindowed 函数。无需定制解决方案。


    这里是一个惰性批处理扩展函数的实现,它将接受一个集合,或者任何可以成为Sequence 并返回SequenceList 每个大小的对象,最后一个是那个大小或更小。

    批量迭代列表的示例用法:

    myList.asSequence().batch(5).forEach { group ->
       // receive a Sequence of size 5 (or less for final)
    }
    

    List 批量转换为Set 的示例:

    myList.asSequence().batch(5).map { it.toSet() }
    

    请参阅下面的第一个测试用例,以显示给定特定输入的输出。

    函数Sequence<T>.batch(groupSize)的代码:

    public fun <T> Sequence<T>.batch(n: Int): Sequence<List<T>> {
        return BatchingSequence(this, n)
    }
    
    private class BatchingSequence<T>(val source: Sequence<T>, val batchSize: Int) : Sequence<List<T>> {
        override fun iterator(): Iterator<List<T>> = object : AbstractIterator<List<T>>() {
            val iterate = if (batchSize > 0) source.iterator() else emptyList<T>().iterator()
            override fun computeNext() {
                if (iterate.hasNext()) setNext(iterate.asSequence().take(batchSize).toList())
                else done() 
            }
        }
    }
    

    单元测试证明它有效:

    class TestGroupingStream {
    
        @Test fun testConvertToListOfGroupsWithoutConsumingGroup() {
            val listOfGroups = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).asSequence().batch(2).toList()
            assertEquals(5, listOfGroups.size)
            assertEquals(listOf(1,2), listOfGroups[0].toList())
            assertEquals(listOf(3,4), listOfGroups[1].toList())
            assertEquals(listOf(5,6), listOfGroups[2].toList())
            assertEquals(listOf(7,8), listOfGroups[3].toList())
            assertEquals(listOf(9,10), listOfGroups[4].toList())
        }
    
        @Test fun testSpecificCase() {
            val originalStream = listOf(1,2,3,4,5,6,7,8,9,10)
    
            val results = originalStream.asSequence().batch(3).map { group ->
                group.toList()
            }.toList()
    
            assertEquals(listOf(1,2,3), results[0])
            assertEquals(listOf(4,5,6), results[1])
            assertEquals(listOf(7,8,9), results[2])
            assertEquals(listOf(10), results[3])
        }
    
    
        fun testStream(testList: List<Int>, batchSize: Int, expectedGroups: Int) {
            var groupSeenCount = 0
            var itemsSeen = ArrayList<Int>()
    
            testList.asSequence().batch(batchSize).forEach { groupStream ->
                groupSeenCount++
                groupStream.forEach { item ->
                    itemsSeen.add(item)
                }
            }
    
            assertEquals(testList, itemsSeen)
            assertEquals(groupSeenCount, expectedGroups)
        }
    
        @Test fun groupsOfExactSize() {
            testStream(listOf(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15), 5, 3)
        }
    
        @Test fun groupsOfOddSize() {
            testStream(listOf(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18), 5, 4)
            testStream(listOf(1,2,3,4), 3, 2)
        }
    
        @Test fun groupsOfLessThanBatchSize() {
            testStream(listOf(1,2,3), 5, 1)
            testStream(listOf(1), 5, 1)
        }
    
        @Test fun groupsOfSize1() {
            testStream(listOf(1,2,3), 1, 3)
        }
    
        @Test fun groupsOfSize0() {
            val testList = listOf(1,2,3)
    
            val groupCountZero =   testList.asSequence().batch(0).toList().size
            assertEquals(0, groupCountZero)
    
            val groupCountNeg =  testList.asSequence().batch(-1).toList().size
            assertEquals(0, groupCountNeg)
    
        }
    
        @Test fun emptySource() {
            listOf<Int>().asSequence().batch(1).forEach { groupStream ->
                fail()
            }
    
        }
    }
    

    【讨论】:

    • 很明显这可以不偷懒,例如映射到it.toList() 或使用Collection 而不是序列的示例。但是因为很容易将它们变成Sequence,所以这是一个很好的起点。
    • 仅供参考:我认为您的 batch 代码中存在错误。以下内容陷入无限循环listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).asSequence().batch(2).toList()
    • 固定,最简单的答案是让每个批次在下一个组迭代时创建一个List。如果您像.batch(2).toList() 一样编写代码,您将同时将所有组作为单独的List 实现到内存中,否则一次只能实现一个组。代码已修复,针对这种情况更新了测试。
    • 这是一个非常好的解决方案,感谢@JaysonMinard!
    • 很棒的答案。它非常有用。谢谢!
    【解决方案3】:

    一个更简单/功能风格的解决方案是

    val items = (1..100).map { "foo_${it}" }
    
    fun <T> Iterable<T>.batch(chunkSize: Int) =
       withIndex().                        // create index value pairs
       groupBy { it.index / chunkSize }.   // create grouping index
       map { it.value.map { it.value } }   // split into different partitions
    
    
    items.batch(3)
    

    注意 1:我个人更喜欢 partition 作为这里的方法名称,但它已经出现在 Kotlin 的 stdlib 中,用于在给定谓词的情况下将列表分成 2 部分。

    注意 2:Jayson 的迭代器解决方案对于大型集合的扩展性可能比此解决方案更好。

    【讨论】:

      【解决方案4】:

      在 Kotlin 1.2 M2 及更高版本中,您可以使用 chunkedwindowed(请参阅 Kotlin 1.2 M2 is out | Kotlin Blog)。请注意,Sequence 也存在差异(请参阅kotlin.sequences - Kotlin Programming Language)。

      对于 1.2 M2 之前的 Kotlin 版本,我建议使用来自 google-guavaLists.partition(List, int)(它使用 java.util.List.subList(int, int)):

      如果您不熟悉Guava,请参阅CollectionUtilitiesExplained · google/guava Wiki 了解更多详情。

      如果需要,您可以为它创建自己的 Kotlin extension function

      fun <T> List<T>.collate(size: Int): List<List<T>> = Lists.partition(this, size)
      

      如果你想要一个可变列表的扩展函数,那么在一个单独的 Kotlin 文件中(以避免平台声明冲突):

      fun <T> MutableList<T>.collate(size: Int): List<MutableList<T>> = Lists.partition(this, size)
      

      如果你想像Jayson Minard's answer 那样延迟加载,你可以使用Iterables.partition(Iterable, int)。如果您想填充最后一个子列表(如果它小于指定的size),您可能还对Iterables.paddedPartition(Iterable, int) 感兴趣。这些返回 Iterable&lt;List&lt;T&gt;&gt;(我认为将其设为 Iterable&lt;Iterable&lt;T&gt;&gt; 并没有多大意义,因为 subList 返回一个有效的视图)。

      如果由于某种原因您不想依赖 Guava,您可以使用您提到的 subList 函数轻松推出自己的产品:

      fun <T> List<T>.collate(size: Int): List<List<T>> {
          require(size > 0)
          return if (isEmpty()) {
              emptyList()
          } else {
              (0..lastIndex / size).map {
                  val fromIndex = it * size
                  val toIndex = Math.min(fromIndex + size, this.size)
                  subList(fromIndex, toIndex)
              }
          }
      }
      

      fun <T> List<T>.collate(size: Int): Sequence<List<T>> {
          require(size > 0)
          return if (isEmpty()) {
              emptySequence()
          } else {
              (0..lastIndex / size).asSequence().map {
                  val fromIndex = it * size
                  val toIndex = Math.min(fromIndex + size, this.size)
                  subList(fromIndex, toIndex)
              }
          }
      }
      

      【讨论】:

      • 我想你也可以使用@JvmName,而不是单独的文件。详情请见Handling signature clashes with @JvmName
      • 我喜欢最后一个简短的答案,它适用于 ArrayLists,而变体也适用于数组。其他列表可能性能较差(即 LinkedList)。而对于其他收藏是不可能的。
      • 您能否更改答案以保护size 为 0(除以零)或负数时的行为。
      • 好主意@JaysonMinard。谢谢!新增require(size &gt; 0)(类似于Guava的分区方式)。
      • 很好,两个不错的不同答案 + Guava,如果人们愿意的话。
      【解决方案5】:

      虚拟数组

       for (i in 0..49){
                   var  data="java"
                  }
                  array.add(data)
      

      使用:

        var data=array?.chunked(15)
      

      kotlin's method

      【讨论】:

        【解决方案6】:

        不幸的是,还没有内置函数,虽然其他答案中的基于函数和 Sequence 的实现看起来不错,如果你只需要 Lists 的 List,我建议写一个一点点丑陋、命令式但高性能的代码。

        这是我的最终结果:

        fun <T> List<T>.batch(chunkSize: Int): List<List<T>> {
            if (chunkSize <= 0) {
                throw IllegalArgumentException("chunkSize must be greater than 0")
            }
            val capacity = (this.size + chunkSize - 1) / chunkSize
            val list = ArrayList<ArrayList<T>>(capacity)
            for (i in 0 until this.size) {
                if (i % chunkSize == 0) {
                    list.add(ArrayList(chunkSize))
                }
                list.last().add(this.get(i))
            }
            return list
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-05-05
          • 2023-04-09
          • 2011-03-24
          • 1970-01-01
          • 2012-07-12
          • 2013-08-18
          相关资源
          最近更新 更多