たとえば、次のようなマークアップされたセカンドレベルまで階層のあるリストを表現したテキストがあったとして、 それを 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階層以上など深くなったときにはどうなるのか? この方法では複雑になりすぎるのかもしれない。(自問自答)