#include <iostream> //头文件
using namespace std; //命名空间
int main(void) //主函数
{
cout << "Hellow world" << endl;
system("pause");
return 0;
}
1、头文件:可以认为头文件是你在调用函数时的一个桥梁。c++的程序是由一个又一个的函数组成的,比如说刚刚这个程序里就可以说包含了两个函数(其中一个函数包含在另一个函数里头),一个是主函数(main函数),还有一个是输出函数cout。在c++中,除了主函数及自定义的函数,其他的函数都是包含于某些头文件里的,它们被称为库函数,想要调用这些函数,只要在程序的开头写上包含该函数的头文件就可以了。比如说包含cout函数的头文件是
2、命名空间:在程序中必须要写上这么一句话即可,是为防止不同人写的函数出现重名,然后将这些函数整合到一起会出现问题的情况。
3、主函数:这是整个程序最为核心,也是最重要的部分。因为它是系统默认的你这个程序运行的入口,换句话说,当你的源代码通过编译,成为一个程序,在计算机上运行时,它是从int main()(其中int 代表这个函数的返回值类型)这里开始的,一句一句往下走。如果没有这个主函数,程序就运行不了。
4、函数体:主函数声明后,下面有一对花括号({ }),花括号里面 所表达的就是你这个函数想要干什么。比如说这一个程序,他的main函数想要做的是输出hello world,因为它调用了一个输出函数 (cout)。有一个非常重要的点就是每一个函数体都必须在一对花括号内,只有这样,系统才能真正明白你想要表达什么(编译才能通过)。
5、返回值:本程序主函数的最后有一句话:return 0,它的意思是 主函数结束后向操作系统返回一个0值,也就是说,如果你的程序顺利地结束了,你的操作系统会得到一个0值,如果运行出错,那么得到的就也许是另外一个值了。
方便自己和他人阅读,不会被程序执行。
//单行
/*多行注释*/
作用:给一段指定的内存空间起名,方便操作这段内存。 语法:数据类型 变量名 = 初始值;
int a = 10;
cout << "a = "<< a << endl;
#define 常量名 常量值
通常在文件上方定义,表示一个常量。
const 数据类型 常量名 = 常量值
通常在变量定义之前加关键字const,修饰该变量为常量,不可修改。
#define day 7//是不可修改的值,一旦修改就会报错
const int month = 30;
C++规定在创建一个变量或者常量的时候,必须要指定出相应的数据类型,否则无法给该变量分配内存空间。
数据类型:整型、sizeof关键字、实型(浮点型)、字符型、转义字符、字符串、布尔类型
为了提高R程序的运行效率,可以尽量使用向量化编程,减少循环,尽量使用内建函数。对于效率的瓶颈,尤其是设计迭代算法时,可以采用编译代码,而Rcpp扩展包可以很容易地将C++代码连接到R程序中,并且支持在C++中使用类似于R的数据类型。
Rcpp可以很容易地把C++代码与R程序连接在一起,可以从R中直接调用C++代码而不需要用户关心那些繁琐的编译、链接、接口问题。可以在R数据类型和C++数据类型之间容易地转换。
在用Rcpp开发之前,要先安装c++编译器
Windows用户需要安装Rtools包;Mac系统的用户可以从应用商店安装Xcode软件,Linux操作系统可以在操作系统命令行用如下命令安装编译软件:
sudo apt-get install r-base-dev
通过执行以下代码
install.packages("Rcpp")
考虑用C++程序计算Fibonacci数的问题。可以使用如下R代码,其中有一部分C++代码,用cppFunction转换成了R可以调用的同名R函数。
library(Rcpp)
cppFunction(code='
int fibonacci(const int x){
if(x < 2) return x;
if(x > 40) return -1;
// 太大的x值会占用过多计算资源
return ( fibonacci(x-1) + fibonacci(x-2) );
}
')
print(fibonacci(5))
## [1] 5
编译、链接、导入是在后台由Rcpp控制自动进行的,不需要用户去设置编译环境,也不需要用户执行编译、链接、导入R的工作。在没有修改C++程序时,同一R会话期间重新运行不必重新编译。
Rcpp属性(attributes)用来简化把C++函数变成R函数的过程。做法是在C++源程序中加入一些特殊注释,利用其指示自动生成C++与R的接口程序。属性是C++11标准的内容,现在的编译器支持还不多,所以在Rcpp支持的C++程序中写成了特殊格式的注释。
Rcpp属性有如下优点: 降低了同时使用R与C++的学习难度; 取消了很多繁复的接口代码; 可以在R会话中很简单地调用C++代码,不需要用户自己考虑编译、连接、接口问题; 可以先交互地调用C++,成熟后改编为R扩展包而不需要修改界面代码。
Rcpp属性的主要组成部分如下: 在C++中,提供Rcpp::export标注要输出到R中的C++函数。 在R中,提供sourceCpp(),用来自动编译连接保存在文件或R字符串中的C++代码,并自动生成界面程序把C++函数转换为R函数。
sourceCpp()函数可以用code=指定一个R字符串,字符串的内容是C++源程序, 其中还是用特殊注释//[[Rcpp::export]]标识要导出的C++函数。 如
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector iters(NumericVector x){
int n = x.size();
NumericVector y(n);
y[0] = x[0];
double sign=-1;
for(int t=1; t<n; t++){
sign *= -1;
y[t] = sign*y[t-1] + x[t];
}
return y;
}
')
print(iters(1:5))
## [1] 1 3 0 4 1
这个例子说明C++程序可以直接写在R程序文件内(保存为R多行字符串),用sourceCpp()函数编译。Rcpp包为C++提供了一个NumericVector数据类型,用来存储数值型向量。用成员函数size()访问其大小,用方括号下标访问其元素。C程序中定义的函数可以返回NumericVector数据类型,将自动转换为R的数值型向量。特殊的注释//[[Rcpp::export]]用来指定哪些C++函数是要转换为R函数的。这叫做Rcpp属性(attributes)功能。
直接把C++代码写在R源程序内部的好处是不用管理多个源文件,缺点是当C++代码较长时,不能利用专用C++编辑环境和调试环境,出错时显示的错误行号不好定位,而且把代码保存在R字符串内,C++代码中用到字符时需要特殊语法。所以,稍复杂的C++代码应该放在单独的C++源文件内。
假设上面的iters函数的C++代码单独存入了一个iters.cpp源文件中,内容如下:
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector iters(NumericVector x){
int n = x.size();
NumericVector y(n);
y[0] = x[0];
double sign=-1;
for(int t=1; t<n; t++){
sign *= -1;
y[t] = sign*y[t-1] + x[t];
}
return y;
}
用如下的sourceCpp()函数把C++源文件中代码编译并转换为R可访问的同名函数, 测试调用:
sourceCpp(file='iters.cpp')
print(iters(1:5))
Rcpp包为C++定义了NumericVector,IntegerVector,CharacterVector, Matrix等新数据类型,可以直接与R的numeric,charactor,matrix对应。 Rcpp最基础的R数据类型是RObject,这是NumericVector,IntegerVector等的基类,通常不直接使用。RObject包裹了原来R的C API的SEXP数据 结构,并且提供了自动的内存管理,不再需要用户自己处理建立内存和消除内存的工作。RObject存储数据完全利用R C API的SEXP数据 结构,不进行额外的复制。
RObject有如下导出类: IntegerVector: 整数向量; NumercicVector: 数值向量; LogicalVector: 逻辑向量; CharacterVector: 字符型向量; GenericVector: 列表; ExpressionVector: 表达式向量; RawVector: 元素为raw类型的向量。 IntergerMatrix, NumericMatrix: 整数值或数值矩阵。
在R向量中,如果其元素都是同一类型(如整数、双精度数、逻辑、字符型),则称为原子向量。Rcpp提供了IntegerVector,NumericVector,LogicalVector,CharacterVector等数据类型与R的原子向量类型对应。在C++中可以用[]运算符存取向量元素,也可以用STL的迭代器。用.begin(),.end()等界定范围,用循环或者accumulate等STL算法处理整个向量。
在R中通常不严格区分整数与浮点实数,但是在与C++交互时,C++对整数与实数严格区分,所以RCpp中整数向量与数值向量是区分的。
在R中,如果定义了一个仅有整数的向量,其类型是整数(integer)的,否则是数值型(numeric)的,如:
x <- 1:5
class(x)
## [1] "integer"
y <- c(0, 0.5, 1)
class(y)
## [1] "numeric"
Rcpp可以把R的整数向量传递到C++的IntegerVector中,也可以把C++的IntegerVector函数结果传递回R中变成一个整数向量。也可以在C++中生成IntegerVector向量,填入整数值。
如果一个正整数等于它所有的除本身以外的因子的和,称这个数为完全数。如 6=1+2+3 28=1+2+4+7+14 是完全数。
任务:用C++程序输入前4个完全数偶数,返回到R中。这4个数为6, 28, 496, 8182。 程序:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
IntegerVector wholeNumberA(){
IntegerVector epn(4);
epn[0] = 6; epn[1] = 28;
epn[2] = 496; epn[3] = 8182;
return epn;
}
')
print(wholeNumberA())
## [1] 6 28 496 8182
对IntegerVector类型的x,访问单个下标i格式为x[i],下标从0开始计算,与R向量下标从1开始的规则不同。
从例子看出,可以在C++中建立一个IntegerVector,需指定大小,可以逐个填入数值,直接返回IntegerVector到R即可。
任务:用C++编写函数,从R中输入整数向量,计算其元素乘积(与R的prod()函数功能类似)。程序如下:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
IntegerVector prod1(IntegerVector x){
int prod = 1;
for(int i=0; i < x.size(); i++){
prod *= x[i];
}
return wrap(prod);
}
')
print(prod1(1:5))
## [1] 120
为了获得向量的元素个数, 用x.size()这种方法。 从程序看出, 用IntegerVector从R中接受一个整数值向量时, 不需要显式地转换。 把一个C++整数值返回给R时, 可以用IntegerVector返回,因为返回值原来是一个C++的int类型, 所以需要用Rcpp::wrap()转换一下。在sourceCpp中可以省略Rcpp::部分。
NumericVector类在C++中保存双精度型一维数组,可以与R的实数型向量(class为numeric)相互转换。这是用C++程序与R交互时最常用到的数据类型。x.size()返回元素个数。
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
double ssp(
NumericVector vec, double p){
double sum = 0.0;
for(int i=0; i < vec.size(); i++){
sum += pow(vec[i], p);
}
return sum;
}
')
ssp(1:4, 2)
## [1] 30
从程序看出,用Rcpp属性编译时,C++函数的输入与返回值类型转换有如下规则:R中数值型向量在C++中可以用NumericVector接收; R中单个的实数在C++中可以用double来接收;为了返回单个的实数, 可以直接返回double,也可以返回NumericVector类型, 或者从一个double型用wrap()转换。C++中如果返回NumericVector, 在R中转换为数值型向量。
在自定义R函数时,输入的自变量的值不会被改变,相当于自变量都是局部变量。如果在自定义函数中修改了自变量的值,实际上只能修改自变量的一个副本的值。如
x <- 100
f <- function(x){
print(x); x <- 99; print(x)
}
c(f(x), x)
## [1] 100
## [1] 99
## [1] 99 100
但是,在用Rcpp编写R函数时,因为RObject传递的是指针,并不自动复制自变量值,所以修改自变量值会真的修改原始的自变量变量值。如:
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector f2(NumericVector x){
x[0] = 99.0;
return(x);
}
')
x <- 100
c(f2(x), x)
## [1] 99 99
可见自变量的值被修改了。当然,对这个问题而言,因为输入的是一个标量,只要函数自变量不是NumericVector类型而是用double类型, 则自变量值会被复制,达到值传递的效果,自变量值也就不会被真的修改。如
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector f3(double x){
x = 99.0;
return(wrap(x));
}
')
x <- 100
c(f3(x), x)
## [1] 99 100
C++中Rcpp支持的向量和列表,仍可以用正整数向量作为下标。 例如, 下面的C++函数求指定的元素子集的和:
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
double sumsub(
NumericVector x, IntegerVector i){
NumericVector y = x[i-1];
double s = 0.0;
int n = y.size();
for(int j=0; j<n; j++)
s += y[j];
return s;
}
')
x <- 1:10
sumsub(x, c(1, 3, 5))
## [1] 9
注意在程序中将输入的下标向量减了1,以适应从R规则到C++规则的变化。 Rcpp在输入和输出时会自动在整型和双精度型之间转换, 比如上面例子中输入的x是整型的,要求输入NumericVector是双精度型的, Rcpp会自动转换。 Rcpp也支持逻辑下标, 比如,下面在C++定义的函数可以取出向量的正值的子集:
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector get_positive(NumericVector x){
NumericVector y = x[x > 0];
return y;
}
')
x <- c(1, -1, 2, -2)
get_positive(x)
## [1] 1 2
Rcpp也支持按元素名访问向量元素或向量子集。
NumericMatrix x(n,m)产生一个未初始化的矩阵,元素为double类型。 x.nrow()返回行数,x.ncol()返回列数,x.size()返回元素个数。 下标从0开始计算。为了访问Xij,用x(i,j)的格式:注意,不是x[i,j]也不是x[i][j]。 为了访问x的第i行,用x.row(i)或x(i,); 为了访问x的第j列,用x.column(j)或x(,j)。 transpose(x)返回x的转置。 NumericMatrix::zeros(n)返回n阶元素为0的方阵。 NumericMatrix::eye(n)返回n阶单位阵。
输入一个矩阵,计算各列的向量模,并返回模的最大值。
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
double testfun(NumericMatrix x){
int nr = x.nrow();
int nc = x.ncol();
double y, y1;
y = 0.0;
for(int j=0; j<nc; j++){
y1 = 0.0;
for(int i=0; i<nr; i++){
y1 += pow(x(i,j), 2);
}
if(y1 > y) y = y1;
}
y = sqrt(y);
return y;
}
')
x <- matrix(c(1, 2, 4, 9), 2, 2)
print(x)
## [,1] [,2]
## [1,] 1 4
## [2,] 2 9
print(testfun(x))
## [1] 9.848858
可以用x(,j)这样的格式将矩阵x的第j列取出为普通向量,并可以赋值修改。也可以用x.column(j),用法相同。x(i,)取出第i行为一个普通向量。 比如,下面的C++函数将输入的矩阵的每一行计算累加和:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericMatrix testfun(NumericMatrix x){
NumericMatrix y = clone(x);
for(int j=1; j<x.ncol(); j++){
y(_,j) = y(_,j-1) + x(_,j);
}
return y;
}
')
x <- matrix(c(1, 2, 4, 9), 2, 2)
print(x)
## [,1] [,2]
## [1,] 1 4
## [2,] 2 9
print(testfun(x))
## [,1] [,2]
## [1,] 1 5
## [2,] 2 11
CharacterVector类型可以与R的字符型向量相互交换信息,在C++中其元素为字符串。 字符型缺失值在C++中为R_NAString。R的字符型向量也可以转换为std::vector。 如:
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
CharacterVector f5(){
CharacterVector x(3);
x[0] = "This is a string";
x[1] = "Test";
x[2] = R_NaString;
return(x);
}
')
f5()
## [1] "This is a string" "Test" NA
R中的向量、矩阵、数据框可以有元素名、列名、行名。 这些名字可以借助Named类添加。
例如:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector f6(){
NumericVector x = NumericVector::create(
Named("math") = 82,
Named("chinese") = 95,
Named("English") = 60);
return(x);
}
')
f6()
## math chinese English
## 82 95 60
“Named(元素名)”可以简写成“_元素名”。 如:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector f6b(){
NumericVector x = NumericVector::create(
_["math"] = 82,
_["chinese"] = 95,
_["English"] = 60);
return(x);
}
')
Rcpp提供的List类型对应于R的list(列表)类型, 在C++中也可以写成GenericVector类型。 其元素可以不是同一类型, 在C++中可以用方括号和字符串下标的格式访问其元素。
例如,下面的函数输入一个列表, 列表元素vec是数值型向量, 列表元素multiplier是数值型标量, 返回一个列表, 列表元素sum为vec元素和, 列表元素dsum为vec元素和乘以multiplier的结果:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
List f7(List x){
NumericVector vec = as<NumericVector>(x["vec"]);
double multiplier = as<double>(x["multiplier"]);
double y = 0.0, y2;
for(int i=0; i<vec.length(); i++){
y += vec[i];
}
y2 = y*multiplier;
return(List::create(Named("sum")=y,
Named("dsum")=y2));
}
')
f7(list(vec=1:5, multiplier=10))
## $sum
## [1] 15
##
## $dsum
## [1] 150
上面的程序用了Rcpp::List::create()当场生成List类型, 因为用Rcpp属性功能编译所以可以略写Rcpp::。
Rcpp的DataFrame类用来与R的data.frame交换信息。
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
DataFrame f8(){
IntegerVector vec =
IntegerVector::create(7,8,9);
std::vector<std::string> s(3);
s[0] = "abc"; s[1] = "ABC"; s[2] = "123";
return(DataFrame::create(
Named("x") = vec,
Named("s") = s));
}
')
f8()
## x s
## 1 7 abc
## 2 8 ABC
## 3 9 123
Rcpp的Function类用来接收一个R函数, 并且可以在C++中调用这样的函数。 示例如:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
SEXP dsort(Function sortFun, SEXP x){
return sortFun(x, Named("decreasing", true));
}
')
dsort(sort, c('bb', 'ab', 'ca'))
## [1] "ca" "bb" "ab"
程序用Function对象sortFun接收从R中传递过来的排序函数, 实际调用时传递过来的是R的sort函数。
在C++中调用R函数时, 有名的自变量用“Named(自变量字符串, 自变量值)”的格式给出。
程序中的待排序的向量与排序后的向量都用了SEXP来说明, 即直接传送原始R API指针, 这样可以不管原来类型是什么, 在C++中完全不进行类型转换或复制。
从运行例子看出数值和字符串都正确地按照降序排序后输出了。
R函数可以不作为C++的函数自变量传递进来, 而是直接调用R的函数。 R的许多函数都已经在Rcpp名字空间中输出了, 所以可以直接调用R的各个向量化的函数, 包括随机数函数。 例如, 下面的C++函数返回一个各列为随机数的数值型矩阵:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericMatrix rngCpp(const int N) {
NumericMatrix X(N, 4);
X(_, 0) = runif(N);
X(_, 1) = rnorm(N);
X(_, 2) = rt(N, 5);
X(_, 3) = rbeta(N, 1, 1);
return X;
}')
R的环境是分层的,可以逐层查找变量名对应的内容。 Rcpp的Environment类用来与R环境对应。 可以利用Environment来定位R的扩展包中的函数或数据, 例如下面的程序片段在C++中定位了stats扩展包中的 rnorm函数并进行了调用:
Environment stats("package:stats");
Function rnorm = stats["rnorm"];
return rnorm(3, Named("sd", 100.0));
as<CPP>(RCPP)
:将RCPP数据类型(RCPP)转换成C++数据类型(CPP)
wrap(CPP)
:将C++数据类型(CPP)转换成RCPP数据类型(RCPP)
RCPP | C++ | as | wrap |
---|---|---|---|
Vector |
vector ,list ,deque |
+ | + |
List ,DataFrame |
vector<vector> ,list<vector> ,etc |
+ | + |
NamedVector |
map ,unodered_map |
- | + |
Vector |
set ,unodered_set |
- | + |
RCPP::Vector
与C++序列容器(如:std::vector
,std::list
,std::deque
,etc)的互相转换NumericVector rcpp_vector = {1,2,3,4,5};
// Conversion from Rcpp::Vector to std::vector
std::vector<double> cpp_vector = as< std::vector<double> >(rcpp_vector);
// Conversion from std::vector to Rcpp::Vector
NumericVector v1 = wrap(cpp_vector);
DataFrame
或List
类型的互相转换
using namespace std;
// [[Rcpp::export]]
// A two-dimensional vector with all element vectors of equal length
// can be converted to a DataFrame
vector<vector<double>> cpp_vector_2d_01 = {{1,2},{3,4}};
DataFrame df = wrap(cpp_vector_2d_01);
// A two-dimensional vector with different length of element vectors
// can be converted to a list
vector<vector<double>> cpp_vector_2d_02 = {{1,2},{3,4,5}};
List li = wrap(cpp_vector_2d_02);
std::map<key,value>
和std::unordered_map<key,value>
数据类型转换成命名的Rcpp::Vector
数据类型(key
是元素的名字,value
是元素的值)library(Rcpp)
sourceCpp(code='
#include<Rcpp.h>
#include<map>
#include<unordered_map>
using namespace Rcpp;
// [[Rcpp::export]]
List std_map(){
std::map<std::string, double> map_str_dbl;
map_str_dbl["E"] = 5;
map_str_dbl["A"] = 1;
map_str_dbl["C"] = 3;
map_str_dbl["D"] = 4;
map_str_dbl["B"] = 2;
std::unordered_map<std::string, double> umap_str_dbl;
umap_str_dbl["E"] = 5;
umap_str_dbl["A"] = 1;
umap_str_dbl["C"] = 3;
umap_str_dbl["D"] = 4;
umap_str_dbl["B"] = 2;
List li = List::create(
Named("std::map",map_str_dbl),
Named("std::unordered_map", umap_str_dbl)
);
return(li);
}
')
std_map()
## $`std::map`
## A B C D E
## 1 2 3 4 5
##
## $`std::unordered_map`
## B D C A E
## 2 4 3 1 5
当C++中赋值运算的右侧表达式是一个R对象或R对象的部分内容时,可以隐含地调用as()将其转换成左侧的C++类型。
当C++中赋值运算的左侧表达式是一个R对象或其部分内容时, 可以隐含地调用wrap()将右侧的C++类型转换成R类型。
详见Rcpp属性。 # Rcpp糖
在C++中,向量和矩阵的运算通常需要逐个元素进行,或者调用相应的函数。
Rcpp通过C++的表达式模板(expression template)功能,可以在C++中写出像R中对向量和矩阵运算那样的表达式。
这称为Rcpp糖(sugar)。
R中的很多函数如sin
等是向量化的,Rcpp糖也提供了这样的功能。
Rcpp糖提供了一些向量化的函数如ifelse
,
sapply
等。比如,两个向量相加可以直接写成x + y
而不是用循环或迭代器(iterator)逐元素计算;
若x是一个NumericVector
,用sin(x)
可以返回由x
每个元素的正弦值组成的NumericVector
。
比如,函数
\[f(x,y)=\begin{cases} x^2 & x<y \\ -y^2 & x>=y \end{cases}\] 如下的程序可以在C++中定义一个向量化的版本:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector f11(NumericVector x, NumericVector y){
return ifelse(x < y, x*x, -(y*y));
}
')
f11(c(1, 3), c(4,2))
## [1] 1 -4
上面简单例子中,x < y
是向量化的,x * x
,
y * y
, -(y * y)
都是向量化的。
ifelse
也是向量化的。
Rcpp糖向量化了+, -, *, /
。
设x, y
都是NumericVector
,
则x + y
,x - y
,x * y
,x * y
将返回对应元素进行四则运算后的NumericVector
变量。
向量与标量运算,如x + 2.0
, 2.0 - x
,
x * 2.0
,
2.0 / x
将返回标量与向量每个元素进行四则运算后的NumericVector
变量。
NumericVector res = x * y + y / 2.0;
NumericVector res = x * (y - 2.0);
NumericVector res = x / (y * y);
Rcpp糖扩展了两个元素的比较到两个等长向量的比较,以及一个向量与一个同类型标量的比较,结果是一个同长度的LogicalVector
,比较运算符包括<, >, <=, >=, ==, !=
。
对数值型向量或返回数值型向量的表达式前面加负号,可以返回每个元素取相反数的结果。
对逻辑型向量或返回逻辑型向量的表达式前面加叹号,可以返回每个元素取反的结果。
在C和C++代码中可以调用R中的函数,但是格式比较复杂,而且原来不支持向量化。 Rcpp糖则使得从C++中调用R函数变得和在R调用函数格式类似。
R源程序中提供了许多数学和统计相关的函数, 这些函数可以编译成一个独立的函数库, 供其它程序链接使用,函数内容在R的Rmath.h头文件中有详细列表。
Rcpp糖把R中的数学函数在C++中向量化了,输入一个向量, 结果是对应元素为函数值的向量。自变量类型可以是数值型或整数型。 这些数学函数包括abs, exp, floor, ceil, pow等。 在Rcpp中支持的C++源程序中调用这些函数, 最简单的一种方法是直接在Rcpp 名字空间中使用和R中相同的函数名和调用方法, 类似sqrt这样的函数允许输入一个向量, 对向量的每个元素计算。
比如,下面的程序输入一个向量,计算相应的ceil函数值:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector f10(NumericVector x){
NumericVector y(x.size());
y = ceil(x);
return y;
}
')
f10(c(-2.3, 1.9, 3.2))
## [1] -2 2 4
R中的rxxxx类的函数可以产生各种分布的随机数向量,随机数向量与当前种子有关。 Rcpp属性会自动地维护随机数发生器的状态使其与R同步。 如:
library(Rcpp)
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector f15(){
NumericVector x = rnorm(10, 0.0, 1.0);
return x;
}
')
round(f15(), 2)
## [1] 0.23 -0.88 1.44 1.49 -0.12 0.92 0.95 0.34 -0.73 0.01
Armadillo C++ Library是一种C++的线性代数库(矩阵数学)以取得良好的平衡速度与易用性。有整数,浮点,和复杂的数字支持,以及获取子集,进行三角和统计的功能。
RcppArmadillo包利用rcpp将R与C++结合了起来。
首先,安装RcppArmadillo包
install.packages("RcppArmadillo")
Data Type | Mathematics |
---|---|
mat | Matrix矩阵 |
vec/colvec | Column Vector列向量 |
rowvec | Row Vector行向量 |
cube | Cube 3维矩阵 |
field | 域 |
mat X(n_rows,n_cols);
mat X(n_rows,n_cols,fill_type);
vec X(n_lem);
vec X(n_lem,fill_type);
其中fill_type
是可选的,可以是以下选择
fill_type | 描述 |
---|---|
fill::zeros | 设置所有元素为0 |
fill::ones | 设置所有元素为1 |
fill::eye | 将矩阵对角线元素设置为1,其他元素为0 |
fill::randu | 随机生成服从0-1平均分布的元素 |
fill::randn | 随机生成服从[0,1]正态分布的元素 |
也可以通过函数创建,如:
vec v = randu<vec>(5);
mat x = randu<mat>(5,5);
元素的初始化:
vec v = {1,2,3};
mat A = {{1,3,5},
{2,4,6}}
元素访问:
元素访问 | 描述 |
---|---|
(n) |
对于vec和rowvec,访问第n个元素。对于mat和field,首先把矩阵的下一列接到上一列之下,从而构成一个长列向量,并访问第n个元素。 |
(i,j) |
对于mat和二维field,访问第(i,j)个元素。 |
子集函数访问:
函数 | 描述 |
---|---|
X.diag(k) |
访问矩阵X的第k个对角线(k是可选的,主对角线为k=0,上对角线为k>0,下对角线为k<0) |
X.row(i) |
访问矩阵X的第i行 |
X.col(i) |
访问矩阵X的第i列 |
X.rows(a,b) |
访问矩阵X从第a行到第b行的子矩阵 |
X.cols(c,d) |
访问举证X从第c列到第d列的子矩阵 |
其他详细内容可参考Armadillo操作手册
下面是一个用RcppArmadillo编写的线性回归程序:
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
Rcpp::List fastLm(const arma::mat& X,
const arma::colvec& y) {
int n = X.n_rows, k = X.n_cols;
arma::colvec coef = arma::solve(X, y); // fit model y ~ X
arma::colvec res = y - X*coef; // residuals
// std.errors of coefficients
double s2 = std::inner_product(res.begin(), res.end(), res.begin(), 0.0)/(n - k);
arma::colvec std_err = arma::sqrt(s2 * arma::diagvec(arma::pinv(arma::trans(X)*X)));
return Rcpp::List::create(Rcpp::Named("coefficients") = coef,
Rcpp::Named("stderr") = std_err,
Rcpp::Named("df.residual") = n - k);
}