Rで関数型プログラミング
背景
1年前の自分が、こんなデータを作っていた。
- データ全体は、recordのリスト
- recordは、そのrecordが作成された時刻とdataを納めたリスト
- dataは、entryのリスト
- entryは、TYPE_A, TYPE_B, TYPE_Cの3つの数値ベクトルを納めたリスト。但し、長さ0かもしれない。また、それぞれの長さは異なる。
1 recordをコンソールに出力すると、こんな感じ。
[[2]] [[2]]$time [1] 0.3170583 [[2]]$data [[2]]$data$ENTRYNAME_LSFU [[2]]$data$ENTRYNAME_LSFU$TYPE_A [1] -0.02828374 -0.65252939 [[2]]$data$ENTRYNAME_LSFU$TYPE_B [1] 1.4714831 -0.4096999 -0.2346018 0.4678917 -0.3272068 [[2]]$data$ENTRYNAME_LSFU$TYPE_C numeric(0) [[2]]$data$ENTRYNAME_DZQY [[2]]$data$ENTRYNAME_DZQY$TYPE_A numeric(0) [[2]]$data$ENTRYNAME_DZQY$TYPE_B [1] 1.877881 [[2]]$data$ENTRYNAME_DZQY$TYPE_C numeric(0) [[2]]$data$ENTRYNAME_GZDP [[2]]$data$ENTRYNAME_GZDP$TYPE_A [1] -0.5423344 0.8562093 1.1156591 [[2]]$data$ENTRYNAME_GZDP$TYPE_B [1] -1.54553885 0.04345684 -0.65033450 -0.19447399 [[2]]$data$ENTRYNAME_GZDP$TYPE_C [1] 0.7071812
で、このデータを、(時間、データタイプ、値)を持つデータフレームに変換する必要に迫られた。
解法
素直に考えたらfor文回すか、となるんだけど、Map, Reduceを使う練習をしてみたら、とてもシンプルに書けたので、メモしておく。初めて、関数型プログラミングって便利だ、と思った。
make.for.each.type <- function(entry){ function(type){ v <- entry[[type]] data.frame(type=type, value=if(length(v)) v else NA) } } for.each.entry <- function(entry){ types <- c("TYPE_A", "TYPE_B", "TYPE_C") for.each.type <- make.for.each.type(entry) Reduce(rbind, Map(for.each.type, types)) } for.each.record <- function(record){ cbind(t=record$time, Reduce(rbind, Map(for.each.entry, record$data))) } make.data.frame <- function(record.list){ Reduce(rbind, Map(for.each.record, record.list)) }
考察と結論
for.each.type 関数はentryへの参照を保持したクロージャとして作成しなければならなくなっている。もし、entryの構造が、
- entryは、複数のentry elementから成る。
- entry elementはtype と value を納めたリスト。
という風だったら、素直に書けたはず。さらに、今回は必要なかったけど、結果のデータフレームにエントリー名も必要ならば、entryの構造を、
- entryは、entry nameと複数のentry elementから成る。
とした方が便利だっただろう。
listを木構造を表すように使う場合には、要素名は、xmlのタグのように扱った方が便利みたいだ。但し、これだと、特定の名前を持つentryへのアクセスに 演算子が使えんかったりするので、要所要所で使い分けか。