简体   繁体   中英

Recursive manipulation of list elements in R

I have a nested list in the global environment of a R script.

anno <- list()

anno[['100']] <- list(
    name = "PLACE",
    color = "#a6cee3",
    isDocumentAnnotation = T,
    sublist = list()
)

person_sublist <- list()

person_sublist[['200']] <- list(
  name = "ACTOR",
  color = "#7fc97f",
  isDocumentAnnotation = T,
  sublist = list()
)

person_sublist[['300']] <- list(
  name = "DIRECTOR",
  color = "#beaed4",
  isDocumentAnnotation = T,
  sublist = list()
)

anno[['400']] <- list(
  name = "PERSON",
  color = "#1f78b4",
  isDocumentAnnotation = T,
  sublist = person_sublist
)

While running my process I interactively select elements via the id (100,200, ...). In return a want to add, delete or move elements in the list.

For this reason I thought of using a recursive function to navigate through the list:

searchListId <- function(parent_id = NULL, annotation_system = NULL)
{
  for(id in names(annotation_system))
  {
    cat(paste(id,"\n"))

    if(id == parent_id)
    {
      return(annotation_system[[id]]$sublist)
    }
    else
    {
      if(length(annotation_system[[id]]$sublist) > 0)
      {
        el <- searchListId(parent_id, annotation_system[[id]]$sublist)
        if(!is.null(el))
          return(el)
      }

    }
  }

  return(NULL)
}

searchListId('100', anno)

This functions returns the list() found in the sublist element of the matching element in the 'anno'-list. My problem is the global environment of R. If I manipulate something ( delete, add, move something within the returned sublist ) i need to reset the global variable with <<- . But in the case of a recursive function I only hold the current sublist in the context where the parent_id matches. How could one reference a global nested list in R while navigating though it via an recursive function? Is that even possible in R?

The calls I want to carry out in order to delete, add, or move elements in the list 'anno' are:

deleteListId('100', anno) #Should return the list without the element 100
addListId('400', anno) #Should return the list with a new element nested in '400'
switchListId('400','200', anno) #Should return a list where the elements with the according keys are switched.

The tricky part though is that I don't know how deep the recursive structure is. Normally I would use element references to manipulate them directly but how could a solution for manipulation of nested lists in R look like if I want to use recursion?

If possible, have the recursive function take a list, alter that, and return the new version. The reason I suggest this is because it's idiomatic R. R leans toward being a functional language, and part of that means state-based actions are discouraged. In general, functions should only modify state if that's all they do. For example, scale(x) doesn't affect the value stored in the x variable. But x <- scale(x) does, because the <- function (yes, it's a function) is meant to modify state.

Also, don't worry about memory unless you know it will be a problem based on past experience. Behind the scenes, R is pretty good at preventing needless copying, so trust it to do the right thing. This lets you work with simpler mental models.

A skeleton of how to recursively modify a list, without affecting the original:

anno <- list()

anno[['A1']] <- list(
  sublist = list(
    A3 = list(sublist = NULL),
    A4 = list(sublist = list(A6 = list(sublist = NULL))),
    A5 = list(sublist = NULL)
  )
)

change_list <- function(x) {
  for (i in seq_along(x)) {
    value <- x[[i]]
    if (is.list(value)) {
      x[[i]] <- change_list(value)
    } else {
      if (is.null(value)) {
        x[[i]] <- "this ws null"
      }
    }
  }
  x
}

change_list(anno)
# $A1
# $A1$sublist
# $A1$sublist$A3
# $A1$sublist$A3$sublist
# [1] "something new"
# 
# 
# $A1$sublist$A4
# $A1$sublist$A4$sublist
# $A1$sublist$A4$sublist$A6
# $A1$sublist$A4$sublist$A6$sublist
# [1] "something new"
# 
# 
# 
# 
# $A1$sublist$A5
# $A1$sublist$A5$sublist
# [1] "something new"

If you absolutely need to modify an item in the global namespace, use environments instead of lists.

anno_env <- new.env()
anno_env[["A1"]] <- new.env()
anno_env[["A1"]][["sublist"]] <- new.env()
anno_env[["A1"]][["sublist"]][["A3"]] <- NULL
anno_env[["A1"]][["sublist"]][["A4"]] <- NULL

change_environment <- function(environ) {
  for (varname in ls(envir = environ)) {
    value <- environ[[varname]]
    if (is.environment(value)) {
      change_environment(value)
    } else {
      environ[[varname]] <- "something new"
    }
  }
}

change_environment(anno_env)

anno_env[["A1"]][["sublist"]][["A3"]]
# [1] "something new"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM