R 中列表元素的惰性求值

Lazy Evaluation of a List Element in R

有没有办法延迟加载列表的元素?

我有一个很大的 data.frames 列表,每个列表都需要很长时间才能生成和加载。通常我不会在会话期间使用所有 data.frames,所以希望它们在我使用它们时延迟生成和加载。我知道我可以使用 delayedAssign 创建延迟加载的变量,但这不能应用于列表元素。

以下是无效的可重现示例:

一些需要一段时间才能生成的函数data.frames:

slow_fun_1 <- function(){
  cat('running slow function 1 now \n')
  Sys.sleep(1)
  df<-data.frame(x=1:5, y=6:10)
  return(df)
}

slow_fun_2 <- function(){
  cat('running slow function 2 now \n')
  Sys.sleep(1)
  df<-data.frame(x=11:15, y=16:20)
  return(df)
}

方法 1

my_list <- list()
my_list$df_1 <-slow_fun_1()
my_list$df_2 <-slow_fun_2()
# This is too slow. I might not want to use both df_1 & df_2.

方法 2

my_list_2 <- list()
delayedAssign('my_list_2$df_1', slow_fun_1())
delayedAssign('my_list_2$df_2', slow_fun_2())
# Does not work. Can't assign to a list. 
my_list_2 #output is still an empty list.

这是一个不完美的解决方案。它是不完美的,因为如果不加载所有列表元素,则无法在 Rstudio 控制台中以交互方式使用该列表。具体来说,当输入 $ 时,Rstudio 会运行​​这两个函数。 my_env$df_1 上的 Ctrl+Enter 按预期工作,因此问题在于控制台中的使用。

my_env <- new.env()
delayedAssign('df_1',slow_fun_1(),assign.env = my_env)
delayedAssign('df_2',slow_fun_2(),assign.env = my_env)
# That was very fast!  
get('df_1',envir = my_env)
my_env$df_1
# only slow_fun_1() is run once my_env$df_1 is called. So this is a partial success. 

# however it does not work interactively in Rstudio 
# when the following is typed into the console:
my_env$
# In Rstudio, once the dollar sign is typed, both functions are run.  

# this works interactively in the Rstudio console. 
# But the syntax is less convenient to type.
my_env[['d']] 

这是一种可能的解决方案。这不是懒惰的评估。但是它会在您需要时计算 data.frame (然后将其缓存,因此仅在第一次进行计算)。您可以使用包 memoise 来实现这一点。例如

slow_fun_1 <- function(){
  cat('running slow function 1 now \n')
  Sys.sleep(1)
  df<-data.frame(x=1:5, y=6:10)
  return(df)
}

slow_fun_2 <- function(){
  cat('running slow function 2 now \n')
  Sys.sleep(1)
  df<-data.frame(x=11:15, y=16:20)
  return(df)
}

library(memoise)

my_list <- list()
my_list$df_1 <-memoise(slow_fun_1)
my_list$df_2 <-memoise(slow_fun_2)

并注意 my_list$df_1 等等实际上是给你 data.frame 的函数,所以你的用法应该是这样的:

> my_list$df_1()
running slow function 1 now 
  x  y
1 1  6
2 2  7
3 3  8
4 4  9
5 5 10
> my_list$df_1()
  x  y
1 1  6
2 2  7
3 3  8
4 4  9
5 5 10
> 

请注意,缓存函数仅在第一次进行实际计算。

更新:如果你想在没有函数调用的情况下坚持原来的用法,一种方法是根据列表修改数据结构,例如:

library(memoise)

lazy_list <- function(...){
  structure(list(...), class = c("lazy_list", "list"))
}

as.list.lazy_list <- function(x){
  structure(x, class = "list")
}

generator <- function(f){
  structure(memoise(f), class = c("generator", "function"))
}

`$.lazy_list` <- function(lst, name){
  r <- as.list(lst)[[name]]
  if (inherits(r, "generator")) {
    return(r())
  }
  return(r)
}

`[[.lazy_list` <- function(lst, name){
  r <- as.list(lst)[[name]]
  if (inherits(r, "generator")) {
    return(r())
  }
  return(r)
}

lazy1 <- lazy_list(df_1 = generator(slow_fun_1),
                   df_2 = generator(slow_fun_2),
                   df_3 = data.frame(x=11:15, y=16:20))

lazy1$df_1
lazy1$df_1
lazy1$df_2
lazy1$df_2
lazy1$df_3