如何在R中的rcpp中正确更改ListMatrix的列表

use*_*235 1 indexing r rcpp

我想在rcpp中更改ListMatrix列表的元素,但始终无法做到这一点.请参阅以下玩具示例:

library("Rcpp")
cppFunction('
ListMatrix ListMatrixType(ListMatrix x){
            NumericMatrix a = x(0,0);
            a(0,0) = 100;
            return x;
            }
            ')
x = matrix(list(matrix(0,3,2)),2,2)
a = ListMatrixType(x)
a[[1,1]]
a[[2,2]]
Run Code Online (Sandbox Code Playgroud)

我希望只会a[[1,1]改变,但为什么a[[2,2]]也会改变?

> a[[1,1]]
     [,1] [,2]
[1,]  100    0
[2,]    0    0
[3,]    0    0
> a[[2,2]]
     [,1] [,2]
[1,]  100    0
[2,]    0    0
[3,]    0    0
Run Code Online (Sandbox Code Playgroud)

我必须误解rcpp中的索引规则.所以我的问题是如何正确更改每个列表的元素?我想每个列表都包含一个矩阵.

coa*_*ess 9

TL; DR:欢迎使用共享环境和Rcpp使用指针.

有两种方法可以解决R在共享环境中处理对象的问题,以避免与该函数相关的多米诺骨牌更新.

  1. 执行对象的深层副本,然后更新矩阵列表位置或
  2. 列表中只有唯一的元素(例如没有重复的矩阵)

首先,让我们看看这个共享内存列表的内存分配.为方便起见 - 并且为了避免tracemem()- 我们将使用该lobstr包来探索共享环境.该软件包目前仅限GitHub,可以使用以下方式获取:

install.packages("devtools")
devtools::install_github("r-lib/lobstr")
Run Code Online (Sandbox Code Playgroud)

随着lobstr在手,让我们来看看在矩阵列表的基本内存地址[R ...

x = matrix(list(matrix(0,3,2)),2,2)
lobstr::obj_addrs(x)
# [1] "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
#      ^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^ 
# identical memory addresses for all objects
Run Code Online (Sandbox Code Playgroud)

请注意,所有对象共享相同的内存位置.因此,R将列表中的每个元素视为相同.R选择此行为以减少内存中数据的大小,因为每个元素属于同一共享环境.

这对于Rcpp尤其成问题,因为它正在操纵指针,例如存储器中显示存储位置的位置,而不是 ,例如保持像a int或a 的值的变量double.

由于此指针行为,会发生两个操作:

  1. 矩阵列表中的所有矩阵都会同时更新,并且
  2. 整个对象x更新,无需返回语句.

如果我们稍微修改你的功能,第二点就更明显了.

用于说明指针的修改函数

注意:此函数不再具有返回类型,但我们仍然看到R的环境中的x对象发生了变化.

#include<Rcpp.h>

// [[Rcpp::export]]
void ListMatrixType_pointer(Rcpp::ListMatrix x){
  Rcpp::NumericMatrix a = x(0, 0);
  a(0, 0) = 100;
}
Run Code Online (Sandbox Code Playgroud)

产量

ListMatrixType_pointer(x)
str(x)
# List of 4
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# - attr(*, "dim")= int [1:2] 2 2
Run Code Online (Sandbox Code Playgroud)

请注意,我们不是返回一个值x会自动更新.此外,我们对每个元素仍然具有相同的存储位置,例如

lobstr::obj_addrs(x)
# [1] "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
#      ^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^ 
# identical memory addresses for all objects
Run Code Online (Sandbox Code Playgroud)

矩阵的深拷贝

要绕过相同的内存地址,您可以深度克隆该对象,然后将其保存回ListMatrix.该clone()函数实例化一个新的内存块以存储值.因此,修改一个元素不再会触发多米诺骨牌更新.此外,将克隆对象添加回列表时,内存地址仅针对该元素进行更改.

使用clone()做出深层副本

#include<Rcpp.h>

// [[Rcpp::export]]
void ListMatrixType_clone(Rcpp::ListMatrix x){
  Rcpp::NumericMatrix a = x(0, 0);

  // Perform a deep copy of a into b
  // and, thus, changing the memory address
  Rcpp::NumericMatrix b = Rcpp::clone(a);
  b(0, 0) = 100;

  // Update x with b
  x(0, 0) = b;
}
Run Code Online (Sandbox Code Playgroud) 产量
ListMatrixType_clone(x)
str(x)
# List of 4
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 0 0 0 0 0 0
# $ : num [1:3, 1:2] 0 0 0 0 0 0
# $ : num [1:3, 1:2] 0 0 0 0 0 0
# - attr(*, "dim")= int [1:2] 2 2

lobstr::obj_addrs(x)
# [1] "0x7fceb811a7e8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
#             ^^^^^^^          ^^^^^^^ 
#            different memory addresses
Run Code Online (Sandbox Code Playgroud)

列表中的唯一元素

为了强调对独特元素的需求,考虑在矩阵列表的第一个位置修改矩阵,只有第一个元素的内存地址会改变 ; 其余的地址将保持不变.例如

x[[1, 1]] = matrix(1, 2, 2)
lobstr::obj_addrs(x)
# [1] "0x7fceb811a7e8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
#             ^^^^^^^          ^^^^^^^ 
#            different memory addresses
Run Code Online (Sandbox Code Playgroud)

其他资源

有关此主题的进一步阅读,请参阅下面任何强调与Rcpp相关的指针行为的链接帖子(免责声明:我写了它们):

关于对象大小的其他说明

注意:object.size()utilsBase R中提供的共享环境大小的计算不正确.比照?utils::object.size

该函数仅提供粗略指示:例如,它对于原子矢量应该是合理准确的,但是不检测列表的元素是否被共享.(考虑到字符向量的元素之间的共享,但不考虑单个对象中的字符向量之间的共享.)

lobstr::obj_size(x)
# 424 B
utils::object.size(x)
# 1304 bytes
Run Code Online (Sandbox Code Playgroud)

因此,由于共享环境,列表的对象大小约为1/3.