2015-12-28 7 views
17

大きなリストを処理のためのバッチに分割するGroovy's collateに相当する機能を探しています。私はsubListを見ましたが、これは同様の機能に適合させることができましたが、自分自身を圧延するための組み込みの、あるいはクレイジーな単純な方法を見逃していないことを確認したいと思いました。ここでKotlin:大きいリストを設定されたパーティションサイズのサブリストに変換する

+4

この機能は、Kotlinの将来のバージョン用に提案されています。 https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/window-sliding.md – succcubbus

答えて

26

は、コレクション、またはSequenceになり、最後の1は、そのサイズ以下であることと、ListそのサイズのそれぞれのSequenceを返すことができる何かを取る怠惰なバッチ処理拡張機能の実装です。

バッチとしてリストを反復する使用例:SetListのバッチを変換する

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

例:

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

    } 
} 
+0

明らかに、これは、 'it.toList()'にマップされている例のように、怠惰にすることはできません。シーケンスの代わりに 'Collection'から作業することもできます。しかし、それを 'Sequence'に変換するのは簡単なので、それは良い出発点です。 –

+2

FYI:バッチコードにバグがあると思います。以下は、無限ループの 'listOf(1,2,3,4,5,6,7,8,9,10).asSequence().batch(2).toList()'に詰まっています。 – mfulton26

+0

ええ、私はそれが今、要素が消費されず、リストに変換する場合、イテレータは依然として表示されることがわかります。イテレータを進めて、消費しないアイテムを保持するためのものを作成する必要があります。 –

6

私はどちらかkotlin-stdlibの1が表示されません。 Guavaは詳細についてはCollectionUtilitiesExplained · google/guava Wikiを見ると、あなたが慣れていない場合

:私は(それがjava.util.List.subList(int, int)を使用しています)google-guavaからLists.partition(List, int)を使用することをお勧めします。

したい場合は、それをあなた自身の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>>として表示されません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) 
     } 
    } 
} 
+0

別々のファイルの代わりに '@ JvmName'を使うこともできます。詳細については、[@JvmNameでのシグネチャの衝突の処理](https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#handling-signature-clashes-with-jvmname)を参照してください。 – mfulton26

+0

私は最後の短い答えが好きです。これはArrayListsでうまく動作し、バリエーションは配列に対してうまく機能します。他のリストは、パフォーマンスが低いかもしれない(すなわち、LinkedList)。他のコレクションでは不可能です。 –

+1

あなたの答えを変更して、 'size'が0(div除算)または負であるときに行動を守ることができます。 –

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:個人的には私はを好むでしょうここでメソッド名としてが使用されていますが、Kotlinのstdlibにはすでにリストが2つの部分に分割されています。

注2:Jaysonのイテレータソリューションは、大規模なコレクションの場合、このソリューションよりも拡張性があります。

0

あり、そのための組み込み関数は、まだ残念ながらありませんし、あなただけのList秒のListである必要がある場合は、機能や他の回答からSequenceベースの実装は、素敵な眺めながら、私は、醜いの少しを書くことをお勧めしたいです命令的だが実行可能なコード。

これは私の最終的な結果である:

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

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]] 
*/ 
関連する問題