Home > OS >  correct way of accessing results from a list from a parent frame in a function call
correct way of accessing results from a list from a parent frame in a function call

Time:08-09

I'm writing a function, which results I want to write into a list. However, each function call requires to use the results from the previous function call.

So I'm unsure about how to best make sure that each function call (I'm using an lapply approach) uses the updated results. i've read about <<- and assign, but still don't really now what the cleanest/correctest way would be.

Example:

My naive/wrong approach is:

results <- list()

my_function <- function(x)
{
  result <- ifelse(length(results) == 0, 0, results[length(results)])
  2 * x   unlist(result)
}

results <- lapply(1:5, my_function)

which returns:

[[1]]
[1] 2

[[2]]
[1] 4

[[3]]
[1] 6

[[4]]
[1] 8

[[5]]
[1] 10

What I would like to return is:

[[1]]
[1] 2

[[2]]
[1] 6

[[3]]
[1] 12

[[4]]
[1] 20

[[5]]
[1] 30

So how do I feed the previous results into the function call?

NOTE: the example function is really a very simple example. I know I could probably do a simple cumsum here. But my real life function is way more complicated and not a simple computation of a number.

CodePudding user response:

The problem is that lapply only updates results after the whole loop is done. Consider the following:

my_function <- function(x)
{
  last_res <- ifelse(length(results) == 0, 0, results[[length(results)]])
  print(last_res)
  2 * x   last_res
}
results <- list()
results <- lapply(1:5, my_function)
[1] 0
[1] 0
[1] 0
[1] 0
[1] 0

Whilst where we use a regular loop:

results <- list()
for(i in 1:5) {
  results[[i]] <- my_function(i)
}
[1] 0
[1] 2
[1] 6
[1] 12
[1] 20

Now, a better way of achieving this would be to add a repeats argument into your function itself.

my_function <- function(repeats=5)
{
  result_list <- list()
  for(i in 1:repeats) {
    last_res <- ifelse(length(result_list) == 0, 0, result_list[[length(result_list)]])
    result_list[[i]] <- 2 * i   last_res
  }
  result_list
}

my_function(5)

[[1]]
[1] 2

[[2]]
[1] 6

[[3]]
[1] 12

[[4]]
[1] 20

[[5]]
[1] 30

Although the loop-based approach is not strictly global assigning as described in the sixth circle of Patrick Burns' R Inferno: doing global assignments, the second approach is cleaner.


Now, if we wanted to dive into the flames, we could use lapply:

my_function <- function(x)
{
  last_res <- ifelse(length(results) == 0, 0, results[[length(results)]])
  print(last_res)
  results[[x]] <<- 2 * x   last_res
}
results <- list()
results <- lapply(1:5, my_function)
[1] 0
[1] 2
[1] 6
[1] 12
[1] 20

Interestingly, using assign falls back into the first behaviour

my_function <- function(x)
{
  last_res <- ifelse(length(results) == 0, 0, results[[length(results)]])
  print(last_res)
  assign('results[[x]]', 2 * x   last_res,  envir=.GlobalEnv)
}
results <- list()
results <- lapply(1:5, my_function)
[1] 0
[1] 0
[1] 0
[1] 0
[1] 0

It seems that even with assign, lapply only updates the full thing after completion.

This can be explained as from ?assign

assign does not dispatch assignment methods, so it cannot be used to set elements of vectors, names, attributes, etc.

Note that assignment to an attached list or data frame changes the attached copy and not the original object: see attach and with.

  • Related