rjstreer

How to recursively rename a list based on its list items


I'd like to recursively rename (or name, as those items are currently unnamed) a list() based on its items (here text). There are several similar questions, however I haven't found one with a list structure as follows and I can't seem to find a general recursive approach to solve this.

The example data comes from here:

nodes <- list(
  list(
    text = "RootA",
    children = list(
      list(
        text = "ChildA1"
      ),
      list(
        text = "ChildA2"
      )
    )
  ),
  list(
    text = "RootB",
    children = list(
      list(
        text = "ChildB1"
      ),
      list(
        text = "ChildB2"
      )
    )
  )
)
# hard coded solution:
names(nodes) <- c(nodes[[1]]$text, nodes[[2]]$text)
names(nodes[[1]]$children) <- c(nodes[[1]]$children[[1]]$text, nodes[[1]]$children[[2]]$text)
names(nodes[[2]]$children) <- c(nodes[[2]]$children[[1]]$text, nodes[[2]]$children[[2]]$text)
str(nodes)

Expected output:

List of 2
 $ RootA:List of 2
  ..$ text    : chr "RootA"
  ..$ children:List of 2
  .. ..$ ChildA1:List of 1
  .. .. ..$ text: chr "ChildA1"
  .. ..$ ChildA2:List of 1
  .. .. ..$ text: chr "ChildA2"
 $ RootB:List of 2
  ..$ text    : chr "RootB"
  ..$ children:List of 2
  .. ..$ ChildB1:List of 1
  .. .. ..$ text: chr "ChildB1"
  .. ..$ ChildB2:List of 1
  .. .. ..$ text: chr "ChildB2"

Edit: I just benchmarked the three answer given on my system. The function provided by @knitz3 seems to be the fastest. Thanks everyone - I learned a lot.

Unit: microseconds
                  expr     min        lq     mean   median        uq     max neval
 list_rename_recursive  46.200   64.7010  458.389   79.601   95.2510 36040.6   100
           modify_tree 886.102 1929.4005 2787.664 2302.801 2779.1010 18778.5   100
            names_text 101.001  207.8015  575.603  246.852  305.9505 30270.8   100

Solution

  • This was fun. I went for something very interpretable. This function loops through every item of the list provided, and calls on itself if an item is itself another list. Should be able to handle unnamed list items as well.

    list_rename_recursive <- function(x) {
    
        # If not a list, return the item
        if (!is.list(x)) {
    
            return(x)
    
        } else {
    
            # If a list, iterate through the items of the list
            for (i in seq_along(x)) {
    
                # If the list item i itself is a list, call
                # the function again. The list item is updated
                # with the returned value with proper name
                # $text if found
                if (is.list(x[[i]])) {
    
                    name_item <- NA
                    if (!is.null(x[[i]]$text)) name_item <- x[[i]]$text
                    x[[i]] <- list_rename_recursive(x[[i]])
                    if (!is.na(name_item)) names(x)[i] <- name_item
    
                }
    
            }
    
            return(x)
    
        }
    
    }
    
    nodes_new <- list_rename_recursive(nodes)
    str(nodes_new)
    
    List of 2
     $ RootA:List of 2
      ..$ text    : chr "RootA"
      ..$ children:List of 2
      .. ..$ ChildA1:List of 1
      .. .. ..$ text: chr "ChildA1"
      .. ..$ ChildA2:List of 1
      .. .. ..$ text: chr "ChildA2"
     $ RootB:List of 2
      ..$ text    : chr "RootB"
      ..$ children:List of 2
      .. ..$ ChildB1:List of 1
      .. .. ..$ text: chr "ChildB1"
      .. ..$ ChildB2:List of 1
      .. .. ..$ text: chr "ChildB2"