我正在尝试将字符 vector (即字符串 vector )从 R 传递到 C/C++ 以用于排序和其他目的。使用 Rcpp 时,可以使用以下代码轻松完成此操作:
#include <Rcpp.h>
#include <vector>
#include <string>
using namespace Rcpp;
// [[Rcpp::export]]
CharacterVector sort(CharacterVector x) {
std::sort(x.begin(), x.end());
return x;
}
但是,由于这是我唯一计划在此包中使用 C++,因此似乎不值得引入对 Rcpp 的依赖。没有它做同样的事情并不容易。整数很简单:
#include <R.h>
#include <Rdefines.h>
#include <algorithm>
#include <string.h>
using namespace std;
SEXP sort(SEXP x) {
int* xx = INTEGER(x);
std::sort(xx, xx+LENGTH(x));
return(x);
}
但是没有std::vector<string>
或 char**
相当于INTEGER()
.
如何在不引入对 Rcpp 的依赖的情况下模拟相同的代码?
这里有讨论如何使用 CHAR(STRING_ELT())
的问题转换单个字符串,但不清楚如何转换为字符串数组/vector 。
最佳答案
STRING_PTR()
类似于 INTEGER()
.它返回 SEXP *
.取消引用值是 R 字符 vector 第一个元素的 SEXP; STRING_PTR(x)[0]
与STRING_ELT(x, 0)
相同. SEXP 本身是指向包含 const char *
的数据结构的指针。到字符 vector 第一个元素的实际字符; 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]
.你想要的是比较实际的空终止 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();
}
也有此问题——输入参数需要在排序前复制。解决方案很简单——复制(并且保护!即使在本示例中在技术上不需要)传入参数。
// [[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
最后,尽管帖子有评论,原始帖子已关闭,但在 StackOverflow 上搜索 [r] STRING_PTR恰好返回一次命中——这个答案。
关于c++ - 将 R 中的 SEXP 转换为 C++ 中的字符串 vector ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34056265/