昨日言ってたところを解決すべく、かなり久しぶりにRの拡張を書いた。Cはもう書く気になれないので、C++で書くことにした。
#include <iostream> #include <fstream> #include <string> #include <vector> #include <R.h> #include <Rdefines.h> #include <Rinternals.h> extern "C" { SEXP my_scan(SEXP fn, SEXP rho) { SEXP result; std::vector<double> v; std::ifstream fis("hoge.txt"); std::string line; while(getline(fis, line)) { double d = atof(line.c_str()); SEXP R_fcall, tmp, value; PROTECT(tmp = allocVector(REALSXP, 1)); REAL(tmp)[0] = d; PROTECT(R_fcall = lang2(fn, tmp)); PROTECT(value = eval(R_fcall, rho)); v.push_back(REAL(value)[0]); UNPROTECT(3); } PROTECT(result = allocVector(REALSXP, v.size())); for(int i = 0; i < v.size(); i++) { REAL(result)[i] = v.at(i); } UNPROTECT(1); return(result); }; }
こいつをこんな感じで使う。
> setwd("/Users/syou6162/") > dyn.load(paste("my_scan", .Platform$dynlib.ext, sep = "")) > > my_scan <- function(f){ + .Call("my_scan", f, new.env()) + } > > my_scan(function(x) {x * 20}) [1] 20 40 100
これで一気に読んだものを変換というのではなく、一行づつ変換というのができるようになったので、でかいデータへの対処がしやすくなるんじゃないかという妄想。エラー処理とかまだ全然書いてないけど、まあこれでとりあえずよいでしょう。
はまったところ
とりあえず日本語の資料が少なすぎて死ぬ!みんな拡張書こうぜ!!
extern
C++で関数を定義するときにはexternを付けておかないと怒られる。
型の変換
C++のdoubleとRのREALSXPを行き来するやり方が分からなくて無駄に時間がかかった。
C++のdoubleをRのREALSXPに代入するときには
REAL(tmp)[0] = d;
のように、RのREALSXPをC++のdoubleに代入するには
d = REAL(value)[0]
のようにやればよさそう。リファレンス系を見ると
SET_VECTOR_ELT(ans, i, eval(R_fcall, rho));
というような、リストの何番目に代入みたいなのばっかり出てきて、ベクトルのほうはなかったので分かるまでに時間がかかった。。。