Kotlin:将大型List转换为set partition size的子列表

时间:2015-12-28 17:57:30

标签: kotlin

我正在寻找一个等同于Groovy's collate的函数,它将大型List分成批处理。我确实看到subList可以改编成类似的功能但想要检查并确保我没有错过内置或疯狂的简单替代方法来滚动我自己。

6 个答案:

答案 0 :(得分:31)

使用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]]
*/

答案 1 :(得分:29)

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

这是一个惰性批处理扩展函数的实现,它将采用一个集合,或任何可以成为Sequence并且返回Sequence每个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()
        }

    }
}

答案 2 :(得分: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-guava中的Lists.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<List<T>>(我没有注意到Iterable<Iterable<T>> 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) } } } 返回有效视图。

如果由于某种原因你不想依赖番石榴,你可以使用你提到的subList功能很容易地自己动手:

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

SELECT A.Form_Id,
      B.CONTAINER_ID,
      A.FORM_DESC,
      A.FORM_TITLE,
      A.LAYOUT,
      A.TOTAL_COLUMNS,
      COUNT (*) Over () AS Total_Rows
      ROW_NUMBER () OVER ( ORDER BY CONTAINER_ID ASC ) ROWNM
    FROM FORM_DEFINITION A
    LEFT JOIN
      (SELECT CONTAINER_ID,FORM_ID FROM FORM_CONTAINER_DEFINITION
      ) B
    ON A.FORM_ID = B.FORM_ID
    AND ( ( UPPER(TRIM(A.FORM_ID)) LIKE '%'
      || UPPER(TRIM('FORM2'))
      ||'%' ) )

答案 3 :(得分:4)

更简单/功能更强的解决方案

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 :(得分:1)

  

虚拟阵列

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

使用过:

  var data=array?.chunked(15)

kotlin's method

答案 5 :(得分:0)

遗憾的是,目前还没有内置函数,而其他答案的功能和基于Sequence的实现看起来很不错,如果你只需要List的{​​{1}} s,我建议写一些丑陋,命令式但高效的代码。

这是我的最终结果:

List