Populate list recursively with lapply in R

Viewed 57

I am struggling to populate a pre-defined 3-level nested list.

lst <- lapply(list("A1"=NULL, "B1"=NULL),
              function(x){
                 lapply(list("Level2"=NULL),
                        function(y){
                            list("Level3"=NULL)})})

str(lst)
List of 2
 $ A1:List of 1
  ..$ Level2:List of 1
  .. ..$ Level3: NULL
 $ B1:List of 1
  ..$ Level2:List of 1
  .. ..$ Level3: NULL

Now, let's say we would like to replace every 3rd level by concatenating the names of the parent levels in order to get:

str(lst)
List of 2
 $ A1:List of 1
  ..$ Level2:List of 1
  .. ..$ Level3: "A1///Level2"
 $ B1:List of 1
  ..$ Level2:List of 1
  .. ..$ Level3: "B1///Level2"

Doing like follows drops the 2nd level names and disorganise the initial list structure:

lst[] <- lapply(names(lst),
                function(x){
                    lapply(names(lst[[x]]),
                           function(y){
                               lst[[x]][[y]][["Level3"]] <- paste(x, y, sep="///")
                           }
                    )
                }
         )

str(lst)
List of 2
 $ A1:List of 1
  ..$ : chr "A1///Level2"
 $ B1:List of 1
  ..$ : chr "B1///Level2"

I am pretty sure it is a basic task, but I don't see why it does not work.

2 Answers

You have replaced element Level3 of the second level of lists with a character vector, which used to be a list.

To get around this problem, just wrap your paste function in list:

Additionally, the problem with using lapply on the names of an object is that the results are not named as the original object. You can use setNames from base R to get around that, but unfortunately, you have to do it at every level:

lst <- setNames(lapply(names(lst),
                function(x){
                    setNames(lapply(names(lst[[x]]),
                           function(y){
                               setNames(lst[[x]][[y]][["Level3"]] <- list(paste(x, y, sep="///")), "Level3")
                           }
                    ), names(lst[[x]]))
                }
         ), names(lst))
str(lst)
#List of 2
# $ A1:List of 1
#  ..$ Level2:List of 1
#  .. ..$ Level3: chr "A1///Level2"
# $ B1:List of 1
#  ..$ Level2:List of 1
#  .. ..$ Level3: chr "B1///Level2"

You can also use rrapply in the rrapply-package (extended version of base rapply), which might be easier to generalize to arbitrarily nested lists:

library(rrapply)

rrapply(
  lst, 
  f = function(x, .xpos) paste(names(lst)[.xpos[1]], names(lst[[.xpos[1]]])[.xpos[2]], sep = "///")
)
#> $A1
#> $A1$Level2
#> $A1$Level2$Level3
#> [1] "A1///Level2"
#> 
#> 
#> 
#> $B1
#> $B1$Level2
#> $B1$Level2$Level3
#> [1] "B1///Level2"

Here, the .xpos variable evaluates to the position in the nested list of the current node under evaluation, so this information can be used to retrieve the names of all ancestor nodes.

Note that for the returned result, the default is how = "replace", which will keep the original list structure, (in base rapply the default is how = "unlist").

Related