R初心者なのですが、tidyverseなどの登場でプログラミングがだいぶ楽になったように思います。R自体はそこそこの計算速度はあるのですが、部分的にはC++で書ければと思いSWIGとc++で関数を書いてみました。
1. C++で関数を書く
例として次のような関数を書いてみました。C++で書いた普通の関数です。
twicefold: 整数ベクトルxsを二倍した値を返す(Rからは浮動小数点型で良いが整数に変換してから計算する)
addv: ベクトルxsにyを加えた値を返す
string_length: 文字列のベクトルを渡し、文字列の長さのベクトルを返す
# include "test.hpp"
using namespace std;
vector<int> twicefold(vector<int> xs){
vector<int> result;
for(const auto& x : xs){
result.push_back(x*2.0);
}
return(result);
}
vector<double> addv(vector<double>xs, double y){
vector<double> result;
for(const auto& x : xs){
result.push_back(x+y);
}
return(result);
}
vector<int> string_length(vector<string> str_ary){
vector<int> lengths;
for(const auto& str : str_ary){
lengths.push_back(str.length());
}
return(lengths);
}
# include <string>
# include <vector>
using namespace std;
vector<int> twicefold(vector<int> xs);
vector<double> addv(vector<double>xs, double y);
vector<int> string_length(vector<string> str_ary);
ヘッダファイルも作っています。Rの関数の引数は、ベクトルを受け取れる方が使える範囲が広くなりますので、なるべくそのように書いた方が便利です。
2. SWIGのインターフェース定義ファイル作成
%module test
%{
# include "test.hpp"
%}
%include <std_string.i>
%include <std_vector.i>
%template(stringVector) std::vector<std::string>;
%include test.hpp // 上の行の%template~より後に
vector<string>の引数や戻り値があるときには、下から2行目の%template...を最終行の%include...の前に入れておいてください。名称は重ならなければ何でも良いです。
vector<int>やvector<double>の場合には必要ないようです。
3. 拡張ライブラリを作成
以上のファイルを同じフィルダに入れて、次のコマンドを実行します。
2行目のCPLUS_INCLUDE_PATHはRdefines.hのあるフォルダを指していますので、環境に応じて書き換えてください。
swig -c++ -r test.i
CPLUS_INCLUDE_PATH=/usr/include/R R CMD SHLIB -fPIC test.cpp test_wrap.cxx
test.Rとtest.so(拡張子はOSにより異なる)ができます。他のファイルもできますが実行にはこの2つのファイルが必要です。
4. テスト用のプログラム
dyn.load(paste("test", .Platform$dynlib.ext, sep=""))
source("test.R")
cacheMetaData(1)
print(twicefold(2))
print(twicefold(c(2,3,4)))
print(addv(c(2,3,4),2))
print(string_length("12345"))
print(string_length(c("12345", "678")))
library(tidyverse)
c(2,3,4) %>% addv(2) %>% print()
最初の3行はライブラリを読み込む時に必要ですので忘れないように。
以上で拡張ライブラリができるはずです。
5. クラスのメンバ関数を作る
C++でクラスを定義し、メンバ関数を作ると最後のtest02.Rに書いてあるような使い方ができます。
上と同じフィイル名を使っていますので、別のフォルダを作成して試してみてください。
# include "test.hpp"
using namespace std;
Test::Test(double x){
num=x;
}
vector<double> Test::mul(vector<double> ys){
vector<double> result;
for(const auto& y : ys){
result.push_back(num*y);
}
return(result);
}
# include <vector>
using namespace std;
class Test{
private:
double num;
public:
Test(double x);
vector<double> mul(vector<double> ys);
};
%module test
%{
# include "test.hpp"
%}
%include <std_vector.i>
%include test.hpp
拡張ライブラリを作成するためのコマンドは上と同じです。
テスト用プログラム
dyn.load(paste("test", .Platform$dynlib.ext, sep=""))
source("test.R")
cacheMetaData(1)
v <- Test(4.0)
print(v$mul(3.3))
print(v$mul(c(2,3.3)))
library(tidyverse)
c(2,3.3) %>% v$mul() %>% print()
CentOS 8.2
macOS 10.13.6
SWIG 4.0.2
R 4.0.2