在 R 中没有复制的情况下修改函数列表的最佳方法

Optimal way to modify a list in a function without copy in R

我正在编写一个包,我需要通过一系列函数修改一个大列表。 实现这一目标的可能方法是什么?

我附上了我的实现,但不确定这是否是最佳的。

##' @export
test <- function(param = TRUE){
  x <- list("a"= data.frame(a1 = c(1,2), a2 = c(1,1)),
            "b"= data.frame(b1 = c(2,3), b2 = c(1,2)))
  message(paste("in test() function, references to x[[1]]:", inspect(x)[["children"]][[1]][["address"]]))
  message(paste("in test() function, references to x[[2]]:", inspect(x)[["children"]][[2]][["address"]]))
  for(name in names(x)) updateList(x, name)
  message(paste("in test() function, post update references to x[[1]]:", inspect(x)[["children"]][[1]][["address"]]))
  message(paste("in test() function, post update references to x[[2]]:", inspect(x)[["children"]][[2]][["address"]]))
  x
}

updateList <- function(x, name){
  message(paste("updateList() references to x[[1]]:", inspect(x)[["children"]][[1]][["address"]]))
  message(paste("updateList() references to x[[2]]:", inspect(x)[["children"]][[2]][["address"]]))
  newdf <- rbind(x[[name]], c(4,4))
  assign("temp", newdf, envir = parent.frame(n = 1))
  with(parent.frame(n = 1), x[[name]] <- temp)
  invisible(NULL)
}

Console,当我运行test()

> library(pryr)
> test()
in test() function, references to x[[1]]: 0x55d66ce9dd98
in test() function, references to x[[2]]: 0x55d670954508
updateList() references to x[[1]]: 0x55d66ce9dd98
updateList() references to x[[2]]: 0x55d670954508
updateList() references to x[[1]]: 0x55d66fca1688
updateList() references to x[[2]]: 0x55d670954508
in test() function, post update references to x[[1]]: 0x55d66fca1688
in test() function, post update references to x[[2]]: 0x55d66ffb8208
$a
  a1 a2
1  1  1
2  2  1
3  4  4

$b
  b1 b2
1  2  1
2  3  2
3  4  4

有没有办法确保 R 没有复制?如何知道它是否在中间创建了副本?

正如@len-greski 所建议的,我们可以看到每个元素的地址,我们可以看到在每次迭代中只有一个数据帧被复制,其余的则没有。

您可以确定 R 是否正在使用 pryr::address()pryr::refs() 复制对象。这里我们将添加message()函数来检查test()updateList()x的地址,以表明正在复制对象。

library(pryr)
test <- function(param = TRUE){
     x <- list("a"= data.frame(a1 = c(1,2), a2 = c(1,1)),
               "b"= data.frame(b1 = c(2,3), b2 = c(1,2)))
     message(paste("in test() function, references to x:",c(address(x)," ",refs(x))))
     for(name in names(x)) updateList(x, name)
     message(paste("in test() function, post update references to x:",c(address(x)," ",refs(x))))
     x
}

updateList <- function(x, name){
     message(paste("updateList() references to x:",c(address(x)," ",refs(x))))
     newdf <- rbind(x[[name]], c(4,4))
     assign("temp", newdf, envir = parent.frame(n = 1))
     with(parent.frame(n = 1), x[[name]] <- temp)
     invisible(NULL)
}

...和输出。

> test()
in test() function, address of x: 0x7fbbcfaf02c8 references: 1
updateList() address of x: 0x7fbbcf696730 references: 0
updateList() address of x: 0x7fbbcf6846c0 references: 0
in test() function, post update address of x: 0x7fbbcfa3d6c8 references: 1
$a
  a1 a2
1  1  1
2  2  1
3  4  4

$b
  b1 b2
1  2  1
2  3  2
3  4  4

> 

从pre-update和post-update之间x地址的变化可以看出是在复制。

在 R 中,修改列表将导致复制整个列表。

您可能需要考虑使用可变的 R6 对象。这里有一些资源:

使用 environment 是一个不错的选择。

k <- lapply(1:100000, identity)
names(k) <- as.character(1:length(k))
f <- function(x, i){x[[i]] <- x[[i]]*2; x}
system.time(for(i in 1:length(k)) f(k, as.character(i)))

这在我的机器上大约需要 2.5 秒。

e2 <- list2env(k, hash = FALSE)
system.time(for(i in 1:length(k)) f(e2, as.character(i)))

environment 需要 0.3 秒。快10倍!!使用 hash = TRUE,速度提高 100 倍。