Kotlin:将大型列表转换为设置分区大小的子列表

我正在寻找一个相当于Groovy的collat​​e函数,它将一个大的列表分割成批处理。 我确实看到subList可以适应类似的功能,但要检查,并确保我没有错过一个内置的或疯狂的简单的替代滚动我自己的。

这是一个惰性批处理扩展函数的实现,它将采集一个集合,或者任何可以成为一个Sequence东西,并且返回一个大小为每个的List Sequence ,最后一个是这个大小或更小。

用作批次迭代列表的示例:

 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() } } } 

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


#1。 使用chunked(size: Int)

 fun main(args: Array<String>) { val list = listOf(2, 4, 3, 10, 8, 7) 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(size: Int, step: Int)

 fun main(args: Array<String>) { val list = listOf(2, 4, 3, 10, 8, 7, 9) val newList = list.windowed(2, 2) //val newList = list.windowed(size = 2, step = 2) // also works println(newList) } /* prints: [[2, 4], [3, 10], [8, 7], [9]] */ 

我也没有在kotlin-stdlib看到一个。 我推荐使用来自google-guava (它使用java.util.List.subList(int, int) )的Lists.partition(List,int ):

如果您不熟悉番石榴,请参阅CollectionUtilitiesExplained·google / guava Wiki以获取更多详细信息。

您可以创建自己的Kotlin 扩展功能 :

 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的答案中加入懒东西,你可以使用Iterables.partition(Iterable,int) 。 如果您想填充最后一个子列表(如果它小于指定的size ),您可能也对Iterables.paddedPartition(Iterable,int)感兴趣。 这些返回Iterable<List<T>> (当subList返回一个有效的视图时,我没有看到太多的使Iterable<Iterable<T>> subList )。

如果由于某种原因,你不想依靠番石榴,你可以很容易地使用你提到的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) } } } 

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

 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:Jayson的迭代器解决方案可能比这个解决方案更适合大型集合。

不幸的是,还没有内置的函数,而从其他答案功能和基于Sequence的实现看起来不错,如果你只是需要List List s,我会建议写一些丑陋的,必要的,但性能码。

这是我的最终结果:

 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 }