我试图将字符向量(即字符串向量)从R传递到C/C++以进行排序和其他目的.使用Rcpp时,可以使用以下代码轻松完成:
#include#include #include using namespace Rcpp; // [[Rcpp::export]] CharacterVector sort(CharacterVector x) { std::sort(x.begin(), x.end()); return x; }
但是,由于这是我在这个软件包中唯一计划使用的C++,因此引入对Rcpp的依赖似乎不值得.没有它就做同样的事情并不容易.整数很容易:
#include#include #include #include using namespace std; SEXP sort(SEXP x) { int* xx = INTEGER(x); std::sort(xx, xx+LENGTH(x)); return(x); }
但没有std::vector
或char**
相当于INTEGER()
.
如何在不向Rcpp引入依赖的情况下模拟相同的代码?
这里有一些问题讨论如何使用CHAR(STRING_ELT())
转换单个字符串,但不清楚如何转换为字符串数组/向量.
STRING_PTR()
类似于INTEGER()
.它返回一个SEXP *
.取消引用该值是R字符向量的第一个元素的SEXP; STRING_PTR(x)[0]
是一样的STRING_ELT(x, 0)
.SEXP本身是指向数据结构的指针,该数据结构包含const char *
字符向量的第一个元素的实际字符; 可以通过访问此指针CHAR(STRING_PTR(x)[i])
.各种宏定义于file.path(R.home("include"), "Rinternals.h")
默认值std::sort(STRING_PTR(x), STRING_PTR(x) + LENGTH(x))
比较参数的取消引用值,即比较元素的指针地址 - STRING_PTR(x)[i] < STRING_PTR(x)[j]
.你想要的是比较实际的以null结尾的C字符串,strcmp(CHAR(STRING_PTR(x)[i]), CHAR(STRING_PTR(x)[j]))
.你的原始std::sort(x.begin(), x.end())
实际上并没有返回排序的字符串(我认为Rcpp的方式是进行排序x.sort()
).
您需要一个自定义比较器,它采用SEXP,提取const char *
(通过CHAR()
宏)并比较这些.这是比较器
struct CMP_CHAR { bool operator()(SEXP x, SEXP y) { return strcmp(CHAR(x), CHAR(y)) < 0; } } cmp_char;
和实施
// [[Rcpp::export]] SEXP sortcpp0(SEXP x) { std::sort(STRING_PTR(x), STRING_PTR(x) + LENGTH(x), cmp_char); return x; }
(我在上面的例子中使用Rcpp使其易于继续使用sourceCpp()
,但是这可以被删除,代码使用R CMD SHLIB
或在包中使用而不依赖于Rcpp).
不过,请注意,这是一个非常糟糕的主意,因为来自R传递,而不复制对象的直接操纵打破副本上变化的错觉,并在距离引入动作-在下面,x
并且 y
虽然只甚至改变x
是排序.
> n = 10; set.seed(123); x = y = sample(as.character(1:n)) > sortcpp0(x); y [1] "1" "10" "2" "3" "4" "5" "6" "7" "8" "9" [1] "1" "10" "2" "3" "4" "5" "6" "7" "8" "9"
我相信天真的Rcpp实现
// [[Rcpp::export]] CharacterVector sortRcpp2(CharacterVector x) { return x.sort(); }
也受此影响 - 在排序之前需要复制输入参数.解决方案很简单 - 复制(和PROTECT!即使在本例中技术上不需要)传入的参数.
// [[Rcpp::export]] SEXP sortcpp(SEXP x) { x = PROTECT(Rf_duplicate(x)); std::sort(STRING_PTR(x), STRING_PTR(x) + LENGTH(x), cmp_char); UNPROTECT(x); return x; }
与预期的行为
> n = 10; set.seed(123); x = y = sample(as.character(1:n)) > sortcpp(x); y [1] "1" "10" "2" "3" "4" "5" "6" "7" "8" "9" [1] "3" "8" "4" "7" "6" "1" "10" "9" "2" "5"
我认为有一些微不足道的Rcpp咒语(Rcpp::clone()
感谢@DirkEddelbuettel)也可以复制参数,这应该被认为是C++代码改变的任何参数的必要实践.
@Spacedman指出R的排序可能没问题 - 它快速下降到C,具有相当快的(虽然不是最快)排序实现,并且内置了许多不错的功能(例如,处理NA和不同的系统区域设置;后者以非常微妙的方式影响排序顺序).但是仍然有很大的收获(我对此感到惊讶......)
> library(microbenchmark) > n = 1e4; set.seed(123); x = sample(as.character(1:n)) > identical(sort(x), sortcpp(x)) [1] TRUE > microbenchmark(sort(x), sortcpp(x)) Unit: milliseconds expr min lq mean median uq max neval sort(x) 56.061580 56.563541 57.034674 56.997618 57.667031 59.003068 100 sortcpp(x) 3.542409 3.556655 3.610071 3.582562 3.662196 3.800319 100
最后,尽管对帖子和封闭的原始帖子发表了评论,但搜索[r] STRING_PTR的 StackOverflow 只返回一个命中 - 这个答案.