Home About Contact
TypeScript , Kotlin , Functional Programming

リストを n 個ごとに分割する ファイナルアンサー

リストを n 個ごとに分割する その4 TypeScript 編の続きです。 このポストの元をたどると リストを n 個ごとに分割する kotlin 編にいきつく。

Kotlin で書いた再帰関数によるもの。

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 )

これを途中で fold を使って書き直すとかやりはじめてかえってややこしいことになってしまった。

もっとこれ簡単に記述する方法があるだろうとおもい調べたところ StackOverflow に答えがありました。

Haskell のコードです。

group :: Int -> [a] -> [[a]]
group _ [] = []
group n l
  | n > 0 = (take n l) : (group n (drop n l))
  | otherwise = error "Negative or zero n"

今回はこれを TypeScript に移植してみます。

take を用意

const take = <T>(n: number, l: T[]): T[] => {
    return l.slice(0, n)
}

const list = ['1','2','3','4','5']

console.log( take(2, list) ) // -> [ "1", "2" ]
console.log( take(3, list) ) // -> [ "1", "2", "3" ]

drop を用意

const drop = <T>(n: number, l: T[]): T[] => {
    return l.slice(n)
}

const list = ['1','2','3','4','5']

console.log( drop(2, list) ) // -> [ "3", "4", "5" ]
console.log( drop(3, list) ) // -> [ "4", "5" ]

group を用意

const take = <T>(n: number, l: T[]): T[] => {
    return l.slice(0, n)
}

const drop = <T>(n: number, l: T[]): T[] => {
    return l.slice(n)
}

//const group = <T>(n: number, l: T[]): T[T[]] => {
const group = <T>(n: number, l: T[]): T[][] => {
    if (l.length==0) {
        return []
    } else {
        return [take(n, l)].concat( group(n, drop(n,l)) )
    }
}

const list = ['1','2','3','4','5','6','7','8']
const result = group(3, list)
console.log(result)

n が 0 以上かのチェックは省きます。

実行してみる:

$ deno run main.ts
[ [ "1", "2", "3" ], [ "4", "5", "6" ], [ "7", "8" ] ]

できました。

これだけの話だった。 もしかすると、長いリストを処理する場合はスタックオーバーフローになるのかもしれない。 まあ、いいか。

結論

使用した Deno のバージョン:

$ deno --version
deno 2.4.1 (stable, release, x86_64-unknown-linux-gnu)
v8 13.7.152.6-rusty
typescript 5.8.3

追伸 Kotlin 版

Kotlin にもこれを移植しておきましょう。 とりあえず、ジェネリクスにする前に文字列リスト前提でコードします。

take

val take: (Int, List<String>)->List<String> = { n, l->
    l.take(n)
}

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

drop

val drop: (Int, List<String>)->List<String> = { n,l->
    l.drop(n)
}

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

group

val group: (Int, List<String>)->List<List<String>> = { n,l->
    if (l.size==0) {
        listOf()
    } else {
        val a: List<String> = take(n, l)
        val b: List<List<String>> = group(n, drop(n,l))
        listOf(a) + b
    }
}

val list = listOf("1","2","3","4","5","6","7","8")
println( group(3, list) )

実行する:

$ kotlin main.kts
[[1, 2, 3], [4, 5, 6], [7, 8]]

できました。

ジェネリクスにする

いつも、この関数リテラル(という呼び方でいいのか?)で ジェネリクスできないのか、って思う。 Claude Sonnet 4 さんにジェネリクスへの変換を頼んだけど、普通に fun で実装してきた。

fun <T> take(n: Int, l: List<T>): List<T> {
    return l.take(n)
}

fun <T> drop(n: Int, l: List<T>): List<T> {
    return l.drop(n)
}

fun <T> group(n: Int, l: List<T>): List<List<T>> {
    return if (l.size == 0) {
        listOf()
    } else {
        val a: List<T> = take(n, l)
        val b: List<List<T>> = group(n, drop(n, l))
        listOf(a) + b
    }
}

val list = listOf("1","2","3","4","5","6","7","8")
println( group(3, list) )

実行する:

$ kotlin -version
Kotlin version 2.2.0-release-294 (JRE 17.0.2+8-86)

$ kotlin main.kts
[[1, 2, 3], [4, 5, 6], [7, 8]]

できました。