このポストはすでに古い。 こちら 改訂版 を参照ください。
前回のエントリーの コードを見直しある意味もう少しシンプルに実装してみます。
実行環境や Writer の実装は 以前のエントリーと同じなのでそちらを見てください。
前回のエントリーと同じデータから 出発します。
typealias XlsxRow = Pair<String,Int>
val xlsxRowList2023 = listOf<XlsxRow>(
Pair("Caffe Americano", 500),
Pair("Pike Place Roast", 500),
Pair("Caffe Misto", 500))
val xlsxRowList2024 = listOf<XlsxRow>(
Pair("Caffe Americano", 600),
Pair("Pike Place Roast", 500),
Pair("Caffe Misto", 400))
ヘルパー関数などの定義をします。(前回と同じです。)
typealias Name = String
enum class Year { Y2023, Y2024 }
data class Item(val name: Name, val price: Int, val year: Year)
val toItemList: (List<XlsxRow>, List<XlsxRow>)-> List<Item> = { xlsxRowList2023, xlsxRowList2024->
val itemList2023 = xlsxRowList2023.map { Item(it.first, it.second, Year.Y2023) }
val itemList2024 = xlsxRowList2024.map { Item(it.first, it.second, Year.Y2024) }
itemList2023 + itemList2024
}
val toNameList: (List<Item>)-> List<Name> = { itemList->
itemList.map { it.name }.distinct()
}
val findItems: (List<Item>, Name, Year)-> List<Item> = { itemList, name, year->
itemList.filter { it.name == name && it.year == year }
}
これらを使って商品名の一覧を取得します。
val itemList = toItemList(xlsxRowList2023, xlsxRowList2024)
val nameList = toNameList(itemList)
println(nameList)
実行します。
$ kotlinc -script main.kts
[Caffe Americano, Pike Place Roast, Caffe Misto]
すべての商品名が取得できました。
次に Writer を使って、商品名ごとに 2023年と2024年 の商品アイテムを結びつけてペアで出力します。
nameList.forEach { name->
val items2023 = findItems(itemList, name, Year.Y2023)
val items2024 = findItems(itemList, name, Year.Y2024)
val resultWriter =
returnWriter(items2023).bind { item2023->
returnWriter(items2024).bind { item2024->
Writer.unit(Pair(item2023, item2024), "OK")
}
}
println( "--- ${name} ---" )
println( resultWriter.valueOpt )
println( resultWriter.text )
}
前回とは異なり、bind を入れ子にすることでコードをシンプルにしました。 ここで使用している returnWriter 関数の実装はこれです。
val returnWriter: (List<Item>)->Writer<Item> = { items->
if( items.size>0 ){
Writer.unit(items.first(), "OK")
} else {
Writer.unit("NG")
}
}
findItems 関数を使って商品名と年から Item を探しますが、結果は List<Item> になります。 このとき該当アイテムが見つかればログに OK と書き出した上で、該当アイテムを Writer に包んで返します。もし見つからない場合は NG として扱いたいので Writer.unit("NG") を返します。
それでは実行してみます。
$ kotlinc -script main.kts
--- Caffe Americano ---
Optional[(Item(name=Caffe Americano, price=500, year=Y2023), Item(name=Caffe Americano, price=600, year=Y2024))]
OK / OK / OK
--- Pike Place Roast ---
Optional[(Item(name=Pike Place Roast, price=500, year=Y2023), Item(name=Pike Place Roast, price=500, year=Y2024))]
OK / OK / OK
--- Caffe Misto ---
Optional[(Item(name=Caffe Misto, price=500, year=Y2023), Item(name=Caffe Misto, price=400, year=Y2024))]
OK / OK / OK
うまく商品名で 2023年と2024年のペアを取得できています。
欠損のあるデータに差し替えて実行してみます。
val xlsxRowList2023 = listOf<XlsxRow>(
Pair("Caffe Americano", 500),
Pair("Pike Place Roast", 500),
Pair("Caffe Misto", 500),
Pair("Hot Chocolate", 500))
val xlsxRowList2024 = listOf<XlsxRow>(
Pair("Caffe Americano", 600),
Pair("Pike Place Roast", 500),
Pair("Caffe Misto", 400),
Pair("Cappuccino", 500))
これも前回使用したものと同じですが、 Hot Chocolate と Cappuccino が片方の年にしか存在しないアイテムになっています。
実行してみます。
--- Caffe Americano ---
Optional[(Item(name=Caffe Americano, price=500, year=Y2023), Item(name=Caffe Americano, price=600, year=Y2024))]
OK / OK / OK
--- Pike Place Roast ---
Optional[(Item(name=Pike Place Roast, price=500, year=Y2023), Item(name=Pike Place Roast, price=500, year=Y2024))]
OK / OK / OK
--- Caffe Misto ---
Optional[(Item(name=Caffe Misto, price=500, year=Y2023), Item(name=Caffe Misto, price=400, year=Y2024))]
OK / OK / OK
--- Hot Chocolate ---
Optional.empty
OK / NG
--- Cappuccino ---
Optional.empty
NG
プログラムは通りましたが、商品 Hot Chocolate と Cappuccino の値は Optional.empty になっていて、ログの最後は NG で終わっています。 片方の年に存在している場合でもアイテム情報を得たいので、これでは不都合です。
ということで、 前回同様にアイテムが見つからなかった場合に対応するため、Item そのものを使うのではなく Optional<Item> を使うように変更してみます。 そのためには returnWriter 関数を次のように書きかえます。
val returnWriter: (List<Item>)->Writer<Optional<Item>> = { items->
if( items.size>0 ){
Writer.unit(Optional.of(items.first()), "OK")
} else {
Writer.unit(Optional.empty(), "NG")
}
}
まず、シグニチャを (List<Item>)->Writer<Optional<Item>> に変更。 それから該当アイテムが存在する場合はそのアイテムを Optional に包んだ上でそれを Writer に包んで返します。 該当アイテムが存在しない場合は、エラー扱いの Writer.unit("ログ") を呼ぶ代わりに Optional.empty() を渡してそれを Writer に包んで返します。
変更はこれだけです。 それでは、実行してみます。
$ kotlinc -script main.kts
--- Caffe Americano ---
Optional[(Optional[Item(name=Caffe Americano, price=500, year=Y2023)], Optional[Item(name=Caffe Americano, price=600, year=Y2024)])]
OK / OK / OK
--- Pike Place Roast ---
Optional[(Optional[Item(name=Pike Place Roast, price=500, year=Y2023)], Optional[Item(name=Pike Place Roast, price=500, year=Y2024)])]
OK / OK / OK
--- Caffe Misto ---
Optional[(Optional[Item(name=Caffe Misto, price=500, year=Y2023)], Optional[Item(name=Caffe Misto, price=400, year=Y2024)])]
OK / OK / OK
--- Hot Chocolate ---
Optional[(Optional[Item(name=Hot Chocolate, price=500, year=Y2023)], Optional.empty)]
OK / NG / OK
--- Cappuccino ---
Optional[(Optional.empty, Optional[Item(name=Cappuccino, price=500, year=Y2024)])]
NG / OK / OK
欠損のある商品 Hot Chocolate , Cappuccino についても Item 情報を出力できるようになりました。
完成したコード全体を掲載します。
import java.util.Optional
class Writer<T> private constructor(val valueOpt: Optional<T>, var text: String) {
fun <R> bind(f: (T)->Writer<R>): Writer<R> {
return if( this.valueOpt.isPresent() ) {
val v: T = this.valueOpt.get()
val w = f(v)
w.text = "${this.text} / ${w.text}"
w
} else {
Writer.unit(this.text)
}
}
companion object {
fun <T> unit(value: T, text: String): Writer<T>{
return Writer(Optional.of(value), text)
}
fun <T> unit(text: String): Writer<T>{
return Writer(Optional.empty(), text)
}
}
}
typealias XlsxRow = Pair<String,Int>
/*
val xlsxRowList2023 = listOf<XlsxRow>(
Pair("Caffe Americano", 500),
Pair("Pike Place Roast", 500),
Pair("Caffe Misto", 500))
val xlsxRowList2024 = listOf<XlsxRow>(
Pair("Caffe Americano", 600),
Pair("Pike Place Roast", 500),
Pair("Caffe Misto", 400))
*/
val xlsxRowList2023 = listOf<XlsxRow>(
Pair("Caffe Americano", 500),
Pair("Pike Place Roast", 500),
Pair("Caffe Misto", 500),
Pair("Hot Chocolate", 500))
val xlsxRowList2024 = listOf<XlsxRow>(
Pair("Caffe Americano", 600),
Pair("Pike Place Roast", 500),
Pair("Caffe Misto", 400),
Pair("Cappuccino", 500))
typealias Name = String
enum class Year { Y2023, Y2024 }
data class Item(val name: Name, val price: Int, val year: Year)
val toItemList: (List<XlsxRow>, List<XlsxRow>)-> List<Item> = { xlsxRowList2023, xlsxRowList2024->
val itemList2023 = xlsxRowList2023.map { Item(it.first, it.second, Year.Y2023) }
val itemList2024 = xlsxRowList2024.map { Item(it.first, it.second, Year.Y2024) }
itemList2023 + itemList2024
}
val toNameList: (List<Item>)-> List<Name> = { itemList->
itemList.map { it.name }.distinct()
}
val findItems: (List<Item>, Name, Year)-> List<Item> = { itemList, name, year->
itemList.filter { it.name == name && it.year == year }
}
val returnWriter: (List<Item>)->Writer<Optional<Item>> = { items->
if( items.size>0 ){
Writer.unit(Optional.of(items.first()), "OK")
} else {
Writer.unit(Optional.empty(), "NG")
}
}
val itemList = toItemList(xlsxRowList2023, xlsxRowList2024)
val nameList = toNameList(itemList)
println(nameList)
nameList.forEach { name->
val items2023 = findItems(itemList, name, Year.Y2023)
val items2024 = findItems(itemList, name, Year.Y2024)
val resultWriter =
returnWriter(items2023).bind { item2023->
returnWriter(items2024).bind { item2024->
Writer.unit(Pair(item2023, item2024), "OK")
}
}
println( "--- ${name} ---")
println( resultWriter.valueOpt )
println( resultWriter.text )
}
bind で処理をつなげていく部分を入れ子にすることで、コードがシンプルかつ直感的で読みやすくなりました。
ここまでコードをシンプルにできたので、いよいよ、次回は、商品名が重複していたり、微妙に異なる商品名を持つアイテムを結びつけるコードを入れていきたい・・・と思います。
以上です。