在C++中将SEXP从R转换为字符串向量

Converting an SEXP from R into a vector of strings in C++

本文关键字:转换 字符串 向量 C++ 中将 SEXP      更新时间:2023-10-16

我正试图将一个字符向量(即字符串向量)从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);
}

但不存在与CCD_ 3等价的CCD_ 1或CCD_。

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

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

STRING_PTR()类似于INTEGER()。它返回一个SEXP *。对该值进行去引用是R字符向量的第一个元素的SEXP;CCD_ 8与CCD_。SEXP本身是指向数据结构的指针,该数据结构包含指向字符向量的第一元素的实际字符的CCD_;该指针可能被CCD_ 11访问。std::vector<string>2 中定义了各种宏

默认的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是为了方便继续使用char**0,但这可以删除,代码可以用R CMD SHLIB编译,也可以在不依赖Rcpp的情况下在包中使用)。

请注意,这是一个非常糟糕的想法,因为在不复制的情况下直接操作从R传递的对象打破了在更改时复制的错觉,并在一定距离上引入了操作——在下文中,xy发生了更改,即使只对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

最后,尽管帖子上有评论,也有关闭的原始帖子,但在StackOverflow中搜索[r]STRING_PTR只会返回一个点击——这个答案。