将Rcpp对象分配到Rcpp列表会产生最后一个元素的重复项

cry*_*nic 3 c++ rcpp

我试图采取一个Rcpp::CharacterMatrix并将每一行转换为自己的元素Rcpp::List.

但是,我编写的函数有一个奇怪的行为,其中列表的每个条目对应于矩阵的最后一行.为什么会这样?这是一些指针相关的概念吗?请解释.

功能:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
List char_expand_list(CharacterMatrix A) {
  CharacterVector B(A.ncol());

  List output;

  for(int i=0;i<A.nrow();i++) {
    for(int j=0;j<A.ncol();j++) {
      B[j] = A(i,j);
    }

    output.push_back(B);
  }

  return output;
}
Run Code Online (Sandbox Code Playgroud)

测试矩阵:

这是A传递给上述函数的矩阵.

mat = structure(c("a", "b", "c", "a", "b", "c", "a", "b", "c"), .Dim = c(3L, 3L))
mat
#     [,1] [,2] [,3]
# [1,] "a"  "a"  "a" 
# [2,] "b"  "b"  "b" 
# [3,] "c"  "c"  "c"
Run Code Online (Sandbox Code Playgroud)

输出:

上面的函数应该将此矩阵作为输入并返回矩阵行列表,如下所示:

char_expand_list(mat)
# [[1]]
# [1] "a" "a" "a"
#
# [[2]]
# [1] "b" "b" "b"
#
# [[3]]
# [1] "c" "c" "c"
Run Code Online (Sandbox Code Playgroud)

但相反,我得到了一些不同的东西:

char_expand_list(mat)
# [[1]]
# [1] "c" "c" "c"
#
# [[2]]
# [1] "c" "c" "c"
#
# [[3]]
# [1] "c" "c" "c"
Run Code Online (Sandbox Code Playgroud)

可以看出,输出具有最后一个元素,例如对于第一和第二列表元素重复的矩阵行"c".为什么会这样?

coa*_*ess 8

这里发生的事情很大程度上是Rcpp对象如何工作的结果.特别是,CharacterVector充当指向内存位置的指针.通过在for循环外定义此内存位置,结果是"全局"指针.也就是说,当B在循环中发生更新时,随后更新B已经方便地存储在其中的所有变体Rcpp::List.因此,"c"整个列表中的重复行.

有了这个说法,使用任何数据类型是一个非常非常非常糟糕的主意,因为你最终会在不断扩展的对象中来回复制.当Rcpp数据类型隐藏控制R对象的底层时,将发生复制,该对象必须重新创建.因此,您应该尝试以下方法之一:.push_back()RcppSEXP

  • 重新排列Rcpp::CharacterVector创建的位置在第一个for循环内并预分配Rcpp::List空间.
  • 切换到仅使用C++标准库对象,并在最后转换为适当的类型.
    • std::liststd::vector<T>类型T(即std::string)
    • Rcpp::wrap(x)返回正确的对象或修改函数返回类型Rcpp::Liststd::list<std::vector<T> >.
  • 预分配Rcpp::List空间和使用std::vector<T>类型T(即std::string).
  • 在将Rcpp对象存储到列表中之前,预先分配Rcpp::List空间并创建一个clone()Rcpp对象.

选项1

在这里,我们通过将声明移动B到第一个循环中来重新排列函数,预分配列表空间,并正常访问输出列表.

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
Rcpp::List char_expand_list_rearrange(Rcpp::CharacterMatrix A) {
  Rcpp::List output(A.nrow());

  for(int i = 0; i < A.nrow(); i++) {
    Rcpp::CharacterVector B(A.ncol());

    for(int j = 0; j < A.ncol(); j++) {
      B[j] = A(i, j);
    }

    output[i] = B;
  }

  return output;
}
Run Code Online (Sandbox Code Playgroud)

选项2

在这里,我们删除Rcpp::CharacterVector赞成std::vector<std::string>和取代Rcpp::Liststd::list<std::vector<std::string> >.最后,我们将标准对象转换为Rcpp::Listvia Rcpp::wrap().

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
Rcpp::List char_expand_std_to_list(Rcpp::CharacterMatrix A) {
  std::vector<std::string> B(A.ncol());

  std::list<std::vector<std::string> > o;

  for(int i = 0 ;i < A.nrow(); i++) {
    for(int j = 0; j < A.ncol(); j++) {
      B[j] = A(i, j);
    }

    o.push_back(B);
  }

  return Rcpp::wrap(o);
}
Run Code Online (Sandbox Code Playgroud)

赠送:

mat = structure(c("a", "b", "c", "a", "b", "c", "a", "b", "c"), .Dim = c(3L, 3L))
char_expand_std_to_list(mat)
# [[1]]
# [1] "a" "a" "a"
#
# [[2]]
# [1] "b" "b" "b"
#
# [[3]]
# [1] "c" "c" "c"
Run Code Online (Sandbox Code Playgroud)

选项3

或者,你可以保持Rcpp::List,但只是提前声明它预期的大小,仍然使用一个std::vector<T>元素.

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
Rcpp::List char_expand_list_vec(Rcpp::CharacterMatrix A) {
  std::vector<std::string> B(A.ncol());

  Rcpp::List o(A.nrow());

  for(int i = 0; i < A.nrow(); i++) {
    for(int j = 0; j < A.ncol(); j++) {
      B[j] = A(i, j);
    }

    o[i] = B;
  }

  return o;
}
Run Code Online (Sandbox Code Playgroud)

选项4

最后,在为列表预定义空间的情况下,在每次迭代时都会显式克隆数据.

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
Rcpp::List char_expand_list_clone(Rcpp::CharacterMatrix A) {
  Rcpp::CharacterVector B(A.ncol());
  Rcpp::List output(A.nrow());

  for(int i = 0; i < A.nrow(); i++) {

    for(int j = 0; j < A.ncol(); j++) {
      B[j] = A(i, j);
    }

    output[i] = clone(B);
  }

  return output;
}
Run Code Online (Sandbox Code Playgroud)

基准

基准测试结果表明,具有重新布置和预分配空间的选项1表现最佳.亚军第二是选项4,它包括克隆每个向量,然后将其保存到Rcpp::List.

library("microbenchmark")
library("ggplot2")

mat = structure(c("a", "b", "c", "a", "b", "c", "a", "b", "c"), .Dim = c(3L, 3L))

micro_mat_to_list = 
  microbenchmark(char_expand_list_rearrange(mat),
                 char_expand_std_to_list(mat),
                 char_expand_list_vec(mat),
                 char_expand_list_clone(mat))
micro_mat_to_list
# Unit: microseconds
#                             expr   min     lq    mean median     uq    max neval
#  char_expand_list_rearrange(mat) 1.501 1.9255 3.22054 2.1965 4.8445  6.797   100
#     char_expand_std_to_list(mat) 2.869 3.2035 4.90108 3.7740 6.4415 27.627   100
#        char_expand_list_vec(mat) 1.948 2.2335 3.83939 2.7130 5.2585 24.814   100
#      char_expand_list_clone(mat) 1.562 1.9225 3.60184 2.2370 4.8435 33.965   100
Run Code Online (Sandbox Code Playgroud)

基准情节