Kotlin: Convertir list grande a sublist de tamaño de partición establecido

Estoy buscando una function equivalente a la intercalación de Groovy que dividiría una gran list en lotes para su procesamiento. Vi subList que podría adaptarse a una function similar, pero quería comprobar y asegurarme de que no me faltaba una alternativa simple incorporada o loca para hacerla mía.

Aquí hay una implementación de una function de extensión de procesamiento por lotes perezoso que tomará una colección, o cualquier cosa que pueda convertirse en una Sequence y devolverá una Sequence de la List de ese tamaño, siendo la última de ese tamaño o menor.

Ejemplo de uso para iterar una list como lotes:

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

Ejemplo para convertir lotes de List en Set :

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

Vea el primer caso de testing a continuación para mostrar la salida de input específica dada.

Código para la function 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() } } } 

Pruebas unitarias que testingn que funciona:

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

Con Kotlin 1.2-M1, de acuerdo con sus necesidades, puede elegir una de las siguientes forms de resolver su problema.


# 1. Uso 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. Usando 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]] */ 

Tampoco veo uno en kotlin-stdlib . Recomiendo usar Lists.partition (List, int) de google-guava (usa java.util.List.subList(int, int) ):

Si no está familiarizado con la guayaba, consulte CollectionUtilitiesExplained · google / guava Wiki para get más información.

Puede crear su propia function de extensión Kotlin si lo desea:

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

Si desea una function de extensión para lists mutables, entonces en un file Kotlin separado (para evitar conflictos de statement de la plataforma):

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

Si desea cargar algo flojo como en la respuesta de Jayson Minard, puede usar Iterables.partition (Iterable, int) . También podría estar interesado en Iterables.paddedPartition (Iterable, int) si desea rellenar la última sublist si es menor que el size especificado. Estos devuelven Iterable<List<T>> (No veo mucho sentido para hacerlo Iterable<Iterable<T>> como subList devuelve una vista eficiente).

Si por alguna razón no quieres depender de Guava, puedes hacer la tuya fácilmente usando la function subList que mencionaste:

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

o

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

Una solución de estilo más simplist / funcional sería

 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) 

Nota 1: Personalmente, prefiero la partition como nombre de método aquí, pero ya está presente en el file stdlib de Kotlin para separar una list en 2 partes dado un pnetworkingicado.

Nota 2: La solución de iterador de Jayson puede escalarse mejor que esta solución para collections grandes.

Desgraciadamente, todavía no hay una function incorporada y aunque las implementaciones funcionales y basadas en Sequence de otras respuestas se ven bien, si lo que necesitas es List de List , te sugiero que escribas un poco feo, imperativo, pero eficiente. código.

Este es mi resultado final:

 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 } 
  • Script de Kotlin como configuration de Gradle - pase los parameters que no usan la extensión
  • ¿Puedo invocar el complemento noargs de Kotlin desde la command-line o desde Ant?
  • Cómo livedata envía los datos a la actividad si hay algún cambio
  • Kotlin comtesting si la function requiere parámetro de instancia
  • Java Integer.MAX_VALUE vs Kotlin Int.MAX_VALUE
  • registerReceiver () muestra error por método sobrecargado
  • acceso a android.os.storage.StorageVolume
  • Pelusa se bloquea en una class de Java leyendo un object Kotlin
  • Sintaxis de quilates en las interfaces de Kotlin
  • Hacer reference al valor y a los methods de llamada en types de class generics
  • Convertir hilo de Java en Kotlin