Abe
Abe

Reputation: 13524

Find the indices of an element in a nested list?

I have a list like:

mylist <- list(a = 1, b = list(A = 1, B = 2), c = list(C = 1, D = 3))

is there an (loop-free) way to identify the positions of the elements, e.g. if I want to replace a values of "C" with 5, and it does not matter where the element "C" is found, can I do something like:

Aindex <- find_index("A", mylist)
mylist[Aindex] <- 5

I have tried grepl, and in the current example, the following will work:

mylist[grepl("C", mylist)][[1]][["C"]]

but this requires an assumption of the nesting level.

The reason that I ask is that I have a deep list of parameter values, and a named vector of replacement values, and I want to do something like

 replacements <- c(a = 1, C = 5)
 for(i in names(replacements)){ 
    indx <- find_index(i, mylist)
    mylist[indx] <-  replacements[i]
  }

this is an adaptation to my previous question, update a node (of unknown depth) using xpath in R?, using R lists instead of XML

Upvotes: 9

Views: 2766

Answers (3)

Joris C.
Joris C.

Reputation: 6234

This can now also be done using rrapply in the rrapply-package (an extended version of base rapply). To return the position of an element in the nested list based on its name, we can use the special arguments .xpos and .xname. For instance, to look up the position of the element with name "C":

library(rrapply)

mylist <- list(a = 1, b = list(A = 1, B = 2), c = list(C = 1, D = 3))

## get position C-node
(Cindex <- rrapply(mylist, condition = function(x, .xname) .xname == "C", f = function(x, .xpos) .xpos, how = "unlist"))
#> c.C1 c.C2 
#>    3    1

We could then update its value in the nested list with:

## update value C-node
mylist[[Cindex]] <- 5

The two steps can also be combined directly in the call to rrapply:

rrapply(mylist, condition = function(x, .xname) .xname == "C", f = function(x) 5, how = "replace")
#> $a
#> [1] 1
#> 
#> $b
#> $b$A
#> [1] 1
#> 
#> $b$B
#> [1] 2
#> 
#> 
#> $c
#> $c$C
#> [1] 5
#> 
#> $c$D
#> [1] 3

Upvotes: 0

Carlos Cinelli
Carlos Cinelli

Reputation: 11597

Based on this question, you could try it recursively like this:

find_and_replace <- function(x, find, replace){
  if(is.list(x)){
    n <- names(x) == find
    x[n] <- replace
    lapply(x, find_and_replace, find=find, replace=replace)
  }else{
    x
  }
}

Testing in a deeper mylist:

mylist <- list(a = 1, b = list(A = 1, B = 2), c = list(C = 1, D = 3, d = list(C=10, D=55)))
find_and_replace(mylist, "C", 5)
$a
[1] 1

$b
$b$A
[1] 1

$b$B
[1] 2


$c
$c$C  ### it worked
[1] 5

$c$D
[1] 3

$c$d
$c$d$C ### it worked
[1] 5

$c$d$D
[1] 55

Upvotes: 1

Blue Magister
Blue Magister

Reputation: 13363

One method is to use unlist and relist.

mylist <- list(a = 1, b = list(A = 1, B = 2), c = list(C = 1, D = 3))
tmp <- as.relistable(mylist)
tmp <- unlist(tmp)
tmp[grep("(^|.)C$",names(tmp))] <- 5
tmp <- relist(tmp)

Because list names from unlist are concatenated with a ., you'll need to be careful with grep and how your parameters are named. If there is not a . in any of your list names, this should be fine. Otherwise, names like list(.C = 1) will fall into the pattern and be replaced.

Upvotes: 8

Related Questions