C++ 将R中的SEXP转换为C+中的字符串向量+;

C++ 将R中的SEXP转换为C+中的字符串向量+;,c++,r,string,C++,R,String,我试图将字符向量(即字符串向量)从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()

我试图将字符向量(即字符串向量)从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;
}
#包括
#包括
#包括
使用名称空间Rcpp;
//[[Rcpp::导出]]
CharacterVector排序(CharacterVector x){
std::sort(x.begin(),x.end());
返回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;
SEXP排序(sexpx){
int*xx=整数(x);
排序(xx,xx+长度(x));
返回(x);
}
但是没有
std::vector
char**
等价于
INTEGER()

如何在不向Rcpp引入依赖项的情况下模拟相同的代码

这里有一些问题讨论了如何使用
CHAR(STRING_ELT())
转换单个字符串,但不清楚如何转换为字符串数组/向量。

STRING_PTR()
类似于
INTEGER()
。它返回一个
SEXP*
。解引用该值是R字符向量的第一个元素的SEXP
STRING_PTR(x)[0]
STRING_ELT(x,0)
相同。SEXP本身是一个指向数据结构的指针,该数据结构包含一个
常量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]
。您想要的是比较实际的以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是为了使继续使用
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]]
SEXP sortcpp0(SEXP x) {
    std::sort(STRING_PTR(x), STRING_PTR(x) + LENGTH(x), cmp_char);
    return x;
}
// [[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咒语(RCP::CalOne)(/Cord>),感谢DelkdDelBuiTele,用于复制参数,这对于任何被C++代码更改的参数都是一种必需的实践。 @Spacedman指出,R的排序可能还可以——它很快就落在了C上,有一个相当快(虽然不是最快)的排序实现,并且内置了很多很好的功能(例如,处理NA和不同的系统区域设置;后者以非常微妙的方式影响排序顺序)。但仍然有很大的收获(我对此感到惊讶…)


最后,尽管有帖子上的评论和关闭的原始帖子,但在StackOverflow中搜索只返回了一次点击——这是答案。

确切地说,为什么你认为引入对Rcpp的依赖似乎不值得,即使你认为它已经提供了一个可行的答案?询问朋友…另外,请允许我删除Rcpp的标签,因为您询问的是非Rcpp答案。我的软件包是22kB。Rcpp为3MB。另外,有些人(比如我自己的公司)在导入依赖项时有困难。对于一个函数来说,这都是相当大的开销。i)您的包是一个二进制的、预构建的、具有动态库依赖关系的包。ii)您可能已经通过其他依赖项拥有了Rcpp:iii)Rcpp可能是3mb,但仅R一项就可以达到这一数字的几十倍。2015年,以mb为单位的文件大小很少重要。不管怎样,你自己强加的困难,谢谢你的分享。它甚至值得引入对C编译器的依赖吗?C++排序在R排序上有多快?这对您的应用程序是否至关重要?您是否在pure R中分析了代码以了解瓶颈在哪里?当然,这取决于你神秘的“其他目的”是什么,但是你应该先在R中编码它,然后你就有了测试的基础。我怀疑速度的差异可能是R正在使用区域的排序序列,而C++只是使用ASCII。我认为你需要<代码> RCPP::C克隆()/<代码>来强制一个不同的副本。这一点在很多地方都有讨论。非常感谢你提出的这个非常透彻的问题/答案,这将花费我痛苦的几个小时来解决。另外,感谢您帮助解决问题,无论您对问题的质量/性质有何个人意见。
> 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