一気読みじゃなくて、一行づつ読み込んで何かしら変換をかます

昨日言ってたところを解決すべく、かなり久しぶりに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

これで一気に読んだものを変換というのではなく、一行づつ変換というのができるようになったので、でかいデータへの対処がしやすくなるんじゃないかという妄想。エラー処理とかまだ全然書いてないけど、まあこれでとりあえずよいでしょう。

はまったところ

とりあえず日本語の資料が少なすぎて死ぬ!みんな拡張書こうぜ!!

ヘッダーファイル

includeする順番が違うと怒られる。R関係のヘッダーは後ろに持っていかないといけない。

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));

というような、リストの何番目に代入みたいなのばっかり出てきて、ベクトルのほうはなかったので分かるまでに時間がかかった。。。