Home About Contact
Kotlin , Functional Programming

リストを n個ごとに分割する その3

その1その2に続く その3 です。

その1 で独自に makeSubList という再帰関数を定義して解決していたが、 fold を使いさえすれば済む話ではないかと思っていたので、今回試した。

結論としては fold で書き直すことができた。

その1 のコード

// main.kts

tailrec fun makeSubList(acc: MutableList<List<String>>, list:List<String>, n:Int): List<List<String>> {
    return if( list.isEmpty() ){
        acc
    } else {
        val subList = list.take(n)
        acc.add(subList)
        makeSubList(acc, list.drop(n), n)
    }
}

val list = listOf("1","2","3","4","5","6","7","8")
val subList = makeSubList(mutableListOf(), list, 3)
println( subList ) // [[1, 2, 3], [4, 5, 6], [7, 8]]

fold を使った改善版

fold のシグニチャ:

inline fun <T, R> Array<out T>.fold(
  initial: R,
  operation: (acc: R, T) -> R
): R

このコンテキストにおける T,R を わかりやすくするため次のように typealias で定義しておく。

typealias TypeT = String
typealias TypeR = List<List<String>>

それではサブリストを fold を使ってつくるために 必要な initial , operation を定義:

val n = 3

val initial: TypeR = listOf(listOf())

val operation: (TypeR, TypeT)->TypeR = { acc, itemValue->
    if( acc.isEmpty() ){ 
        val newSubList = listOf(itemValue)
        acc + listOf(newSubList)
    } else {
        val lastSubList = acc.last()
        if( lastSubList.size < n ){
            val newSubList = lastSubList + listOf(itemValue)
            acc.take( acc.size-1 ) + listOf(newSubList)
        } else {
            val newSubList = listOf(itemValue)
            acc + listOf(newSubList)
        }
    }
}

この initial , operationfold を使ってサブリストをつくる:

val list =  listOf("1","2","3","4","5","6","7","8")
val subListList = list.fold(initial, operation)
println(subListList) // [1, 2, 3], [4, 5, 6], [7, 8]]

nfold 関数に含めることができないので、 ここではグローバル変数として与えている。 これは少し気持悪い。 そこで、全体を MakeSubList というクラスで囲むことにする。

// main.kts

typealias TypeT = String
typealias TypeR = List<List<String>>

class MakeSubList(private val n: Int){
    val initial: TypeR = listOf(listOf())
    
    val operation: (TypeR, TypeT)->TypeR = { acc, itemValue->
        if( acc.isEmpty() ){ 
            val newSubList = listOf(itemValue)
            acc + listOf(newSubList)
        } else {
            val lastSubList = acc.last()
            if( lastSubList.size < n ){
                val newSubList = lastSubList + listOf(itemValue)
                acc.take( acc.size-1 ) + listOf(newSubList)
            } else {
                val newSubList = listOf(itemValue)
                acc + listOf(newSubList)
            }
        }
    }

    val run: (List<String>)->TypeR = { it.fold(initial, operation) }
}

val list = listOf("1","2","3","4","5","6","7","8")
println(MakeSubList(2).run(list)) // [[1, 2], [3, 4], [5, 6], [7, 8]]
println(MakeSubList(3).run(list)) // [[1, 2, 3], [4, 5, 6], [7, 8]]
println(MakeSubList(4).run(list)) // [[1, 2, 3, 4], [5, 6, 7, 8]]
println(MakeSubList(5).run(list)) // [[1, 2, 3, 4, 5], [6, 7, 8]]

run 関数を定義してそこにリスト(List<String>)を渡すと結果のサブリストが返るようにした。 nMakeSubList のコンストラクタに渡す。

n だけのためにわざわざ MakeSubList クラスをつくるのはちょっと微妙。 いやまて、 クラスを使うのではなく、単純に toSubList 関数を定義すればよいだけではないか? このように:

// main.kts

typealias TypeT = String
typealias TypeR = List<List<String>>

val toSubListList: (List<String>, Int)->TypeR = { list, n->
    val initial: TypeR = listOf(listOf())
    
    val operation: (TypeR, TypeT)->TypeR = { acc, itemValue->
        if( acc.isEmpty() ){ 
            val newSubList = listOf(itemValue)
            acc + listOf(newSubList)
        } else {
            val lastSubList = acc.last()
            if( lastSubList.size < n ){
                val newSubList = lastSubList + listOf(itemValue)
                acc.take( acc.size-1 ) + listOf(newSubList)
            } else {
                val newSubList = listOf(itemValue)
                acc + listOf(newSubList)
            }
        }
    }

    list.fold(initial, operation)
}

val list = listOf("1","2","3","4","5","6","7","8")
println(toSubListList(list, 2)) // [[1, 2], [3, 4], [5, 6], [7, 8]]
println(toSubListList(list, 3)) // [[1, 2, 3], [4, 5, 6], [7, 8]]
println(toSubListList(list, 4)) // [[1, 2, 3, 4], [5, 6, 7, 8]]
println(toSubListList(list, 5)) // [[1, 2, 3, 4, 5], [6, 7, 8]]

これで、サブリスト(リスト)を作り出すために独自の再帰関数ではなく fold を使って実現できました。

subList を使ってサブリストをつくる

その2 の改良。

そもそもサブリスト(リスト)をつくるのに、自前再帰関数や fold 使うなど仰々しいことをしないで subList ( https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/-list/sub-list.html ) を使えば解決できる。

listOf("1","2","3","4","5","6","7","8").subList(0,3)

これは次のリストを生み出す:

[1, 2, 3]

だからこのリストをたとえば 3個ずつのリスト に分割したければ:

val list = listOf("1","2","3","4","5","6","7","8")
list.subList(0,3) // [1, 2, 3]
list.subList(3,6) // [4, 5, 6]
list.subList(6,8) // [7, 8]

最後のサブリスト作成時の from, to index 指定にさえ注意すれば問題ない。

あとは サブリストの開始index のリスト [0, 3, 6] を生み出す方法を考えればよい。 それは次のようにする:

0.until(list.size).step(3).map { it } // [0, 3, 6]

それでは、 この方法で先ほどと同じように使える MakeSubList クラスをつくってみる。

// main.kts

import kotlin.math.min

typealias TypeT = String
typealias TypeR = List<List<String>>

class MakeSubList(private val n: Int){
    val run: (List<TypeT>)->TypeR = { list->
        0.until(list.size).step(n).map { i->
            val fromIndex = i
            val toIndex   = min(fromIndex + n, list.size)
            list.subList(fromIndex, toIndex)
        }
    }
}

val list = listOf("1","2","3","4","5","6","7","8")
println(MakeSubList(2).run(list)) // [[1, 2], [3, 4], [5, 6], [7, 8]]
println(MakeSubList(3).run(list)) // [[1, 2, 3], [4, 5, 6], [7, 8]]
println(MakeSubList(4).run(list)) // [[1, 2, 3, 4], [5, 6, 7, 8]]
println(MakeSubList(5).run(list)) // [[1, 2, 3, 4, 5], [6, 7, 8]]

これだけでサブリスト(リスト)を生成できました。

これならわざわざ MakeSubList クラスをつくるまでもない。 代わりに次の toSubListList 関数だけで事足りる。

import kotlin.math.min

typealias TypeT = String
typealias TypeR = List<List<String>>

val toSubListList: (List<String>, Int)->TypeR = { list, n->
    0.until(list.size).step(n).map { i->
        val fromIndex = i
        val toIndex   = min(fromIndex + n, list.size)
        list.subList(fromIndex, toIndex)
    }
}

val list = listOf("1","2","3","4","5","6","7","8")
println(toSubListList(list, 2)) // [[1, 2], [3, 4], [5, 6], [7, 8]]
println(toSubListList(list, 3)) // [[1, 2, 3], [4, 5, 6], [7, 8]]
println(toSubListList(list, 4)) // [[1, 2, 3, 4], [5, 6, 7, 8]]
println(toSubListList(list, 5)) // [[1, 2, 3, 4, 5], [6, 7, 8]]

以上です。