Kotlin:将大列表转换为设置分区大小的子列表
Kotlin: Convert large List to sublist of set partition size
我正在寻找一个等同于 Groovy's collate which would partition a large List into batches for processing. I did see subList
的函数,它可以改编成类似的函数,但我想检查并确保我没有错过一个内置的或疯狂的简单替代方法来滚动我的拥有。
注意: 对于 Kotlin 1.2 及更新版本,请参阅现在的 chunked
and windowed
函数在标准库中。无需自定义解决方案。
这是一个惰性批处理扩展函数的实现,它将接受一个集合,或者任何可以成为 Sequence
和 return 的 Sequence
of List
的东西每个都是那个尺寸,最后一个是那个尺寸或更小。
批量迭代列表的示例用法:
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 M2 及更高版本中你可以使用 chunked
and windowed
(see Kotlin 1.2 M2 is out | Kotlin Blog). Note that there are Sequence
variances too (see kotlin.sequences - Kotlin Programming Language).
对于 1.2 M2 之前的 Kotlin 版本,我建议使用 Lists.partition(List, int) from google-guava
(it uses java.util.List.subList(int, int)
):
如果您不熟悉Guava see 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)
如果你想要像 you can use Iterables.partition(Iterable, int). You might also be interested in Iterables.paddedPartition(Iterable, int) if you want to pad the last sublist if it is smaller than the specified size
. These return Iterable<List<T>>
(I don't see much point in making it Iterable<Iterable<T>>
as subList
returns 中那样延迟加载的东西,一个有效的视图)。
如果出于某种原因你不想依赖 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)
}
}
}
更simplistic/functional-style的解决方案是
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 的迭代器解决方案可能比此解决方案具有更好的扩展性。
不幸的是,目前还没有 built-in 功能,虽然来自其他答案的功能性和基于 Sequence
的实现看起来不错,但如果您只需要 List
of [=12] =]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
}
使用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]]
*/
Dummy Array
for (i in 0..49){
var data="java"
}
array.add(data)
Used:
var data=array?.chunked(15)
我正在寻找一个等同于 Groovy's collate which would partition a large List into batches for processing. I did see subList
的函数,它可以改编成类似的函数,但我想检查并确保我没有错过一个内置的或疯狂的简单替代方法来滚动我的拥有。
注意: 对于 Kotlin 1.2 及更新版本,请参阅现在的 chunked
and windowed
函数在标准库中。无需自定义解决方案。
这是一个惰性批处理扩展函数的实现,它将接受一个集合,或者任何可以成为 Sequence
和 return 的 Sequence
of List
的东西每个都是那个尺寸,最后一个是那个尺寸或更小。
批量迭代列表的示例用法:
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 M2 及更高版本中你可以使用 chunked
and windowed
(see Kotlin 1.2 M2 is out | Kotlin Blog). Note that there are Sequence
variances too (see kotlin.sequences - Kotlin Programming Language).
对于 1.2 M2 之前的 Kotlin 版本,我建议使用 Lists.partition(List, int) from google-guava
(it uses java.util.List.subList(int, int)
):
如果您不熟悉Guava see 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)
如果你想要像 size
. These return Iterable<List<T>>
(I don't see much point in making it Iterable<Iterable<T>>
as subList
returns 中那样延迟加载的东西,一个有效的视图)。
如果出于某种原因你不想依赖 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)
}
}
}
更simplistic/functional-style的解决方案是
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 的迭代器解决方案可能比此解决方案具有更好的扩展性。
不幸的是,目前还没有 built-in 功能,虽然来自其他答案的功能性和基于 Sequence
的实现看起来不错,但如果您只需要 List
of [=12] =]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
}
使用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]]
*/
Dummy Array
for (i in 0..49){
var data="java"
}
array.add(data)
Used:
var data=array?.chunked(15)