Home About
Kotlin , Text Processing

Kotlin, fold を使って階層のあるリストをパースする

たとえば、次のようなマークアップされたセカンドレベルまで階層のあるリストを表現したテキストがあったとして、 それを kotlin の fold を使ってパースして構造化する例を考える。

パース対象となるリストを記述した index.txt:

- ThinkPad
-- X1 nano
-- X13s
- MacBook
-- Air
-- Pro 14
-- Pro 16

これを fold を使って Brand と Laptop オブジェクトで構造化する。

main.kts:

import java.io.File
import java.io.FileInputStream

data class Laptop(val name: String)
data class Brand(val name: String, val laptopList: List<Laptop>)


val indexFile = File("index.txt")

val brandRegex  = "^- (.*)".toRegex()
val laptopRegex = "^-- (.*)".toRegex()

val brandList: List<Brand> = FileInputStream(indexFile)
    .bufferedReader(Charsets.UTF_8)
    .useLines { lineSequences: Sequence<String> ->

    val initialValue = listOf<Brand>()

    lineSequences.fold(initialValue, { acc, line->
        val matchResultBrand  = brandRegex.find(line)
        val matchResultLaptop = laptopRegex.find(line)

        if( acc.size==0 ){
            if( matchResultBrand!=null ){
                val brandName = matchResultBrand.groupValues[1]
                acc + listOf(Brand(brandName, listOf<Laptop>()))
            } else {
                acc
            }
        } else {
            if( matchResultBrand!=null ){
                val brandName = matchResultBrand.groupValues[1]
                acc + listOf(Brand(brandName, listOf<Laptop>()))
            } else if( matchResultLaptop!=null ){
                val laptopName = matchResultLaptop.groupValues[1]
                val lastOne = acc.last()
                val updatedBrand = Brand(
                    lastOne.name, 
                    lastOne.laptopList + listOf(Laptop(laptopName)))
                acc.take(acc.size-1) + listOf(updatedBrand)
            } else {
                acc
            }
        }
    })
}

brandList.forEach {
    println(it)
}

実行する。

$ kotlinc -script main.kts
Brand(name=ThinkPad, laptopList=[Laptop(name=X1 nano), Laptop(name=X13s)])
Brand(name=MacBook, laptopList=[Laptop(name=Air), Laptop(name=Pro 14), Laptop(name=Pro 16)])

マークダウンなどのマークアップテキストをパースするときにわりと使う。

しかし、階層が3階層以上など深くなったときにはどうなるのか? この方法では複雑になりすぎるのかもしれない。(自問自答)

Liked some of this entry? Buy me a coffee, please.