【发布时间】:2021-12-26 15:32:56
【问题描述】:
假设我们有一个任意大小的大型嵌套列表(深度级别可能超过 100)。该列表包含列表中非预定义位置的对象(最多数千个),我们需要经常查看和修改这些对象。因此,我们在列表中保留一个带有指向这些对象的指针的单独变量。我们需要知道创建指针的最快方法是什么。
到目前为止,我可以用下面的代码想到 4 种不同的解决方案:
首先,为了演示,我们需要一个虚拟的嵌套列表对象:
create_nested_list <- function(depth) {
myList = list()
if(depth > 0) {
depth <- depth - 1
myList$level <- paste0(paste0('depth_', depth))
myList[[paste0('Depth_', depth, '_A')]] <- create_nested_list(depth)
myList[[paste0('Depth_', depth, '_B')]] <- create_nested_list(depth) }
return(myList) }
myList <- create_nested_list(10)
然后,假设我们希望看到并经常修改列表中的以下属性:
myList$Depth_9_B$Depth_8_A$Depth_7_A$Depth_6_A$Depth_5_A$Depth_4_A$Depth_3_A$Depth_2_A$level
-
上面的表达式将是访问列表中元素的直接方法。但是,它不适用于我们的情况,因为上面的代码会创建对象的副本而不是指针。
-
Base-R 解决方案是将对象的路径保存在字符串中并计算表达式。
path <- '$Depth_9_B$Depth_8_A$Depth_7_A$Depth_6_A$Depth_5_A$Depth_4_A$Depth_3_A$Depth_2_A$level'
eval(str2lang(paste0('myList', path)))
- 我们也可以使用库“pointr”来创建指针对象。
library(pointr)
ptr('pointer_to_the_object', 'myList$Depth_9_B$Depth_8_A$Depth_7_A$Depth_6_A$Depth_5_A$Depth_4_A$Depth_3_A$Depth_2_A$level')
pointer_to_the_object
-
我们可以使用 R6/Reference 类,而不是使用 S3 类对象。但在这种情况下,列表中的每个元素都必须是一个单独的 S6 类对象。我们需要改变创建基本列表的方式。
library(R6) nestedR6 <- R6Class( 'myList', cloneable = FALSE, lock_objects = FALSE, public = list( ref_list = NULL, initialize = function(depth) { if(depth > 0) { depth <- depth - 1 self$level <- paste0(paste0('depth_', depth)) self[[paste0('Depth_', depth, '_A')]] <- nestedR6$new(depth) self[[paste0('Depth_', depth, '_B')]] <- nestedR6$new(depth) } } ) )
myListR6 <- nestedR6$new(10)
R6obj <- myListR6$Depth_9_B$Depth_8_A$Depth_7_A$Depth_6_A$Depth_5_A$Depth_4_A$Depth_3_A$Depth_2_A
然后,我们可以比较所有 4 种方法的速度:
library(microbenchmark)
library(ggplot2)
mbm <- microbenchmark(direct = myList$Depth_9_B$Depth_8_A$Depth_7_A$Depth_6_A$Depth_5_A$Depth_4_A$Depth_3_A$Depth_2_A$level,
direct2 = myList[['Depth_9_B']][['Depth_8_A']][['Depth_7_A']][['Depth_6_A']][['Depth_5_A']][['Depth_4_A']][['Depth_3_A']][['Depth_2_A']][['level']],
eval_expression = {
eval(str2lang(paste0('myList', path)))
},
pointer = pointer_to_the_object,
R6_Class = R6obj[['level']],
times = 100)
autoplot(mbm)
令人惊讶的是,通过指针对象的访问是最慢的,而 R6 类指针的工作速度甚至比直接访问还要快。不幸的是,R6 类并不是最佳解决方案,因为通过 R6 对象创建嵌套列表比 S3 慢得多。
microbenchmark(
S3 = create_nested_list(10),
S6 = nestedR6$new(10)
)
【问题讨论】: