リストを 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 に移植してみます。
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" ]
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" ]
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 にもこれを移植しておきましょう。 とりあえず、ジェネリクスにする前に文字列リスト前提でコードします。
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]]
できました。