Stefan Jelkovich
Stefan Jelkovich

Reputation: 193

R: How to set names in a nested list from attributes

Edit: I rewrite this question, as I have two related questions that maybe could be answered better together...


I've got some large nested lists with nearly the same structure and without names. All items of the list have attributes and I want to assign these as names in all levels of the list. Furthermore I want to drop a needless list-level.

So this:

before <- list(list("value_1"), list(list("value_2a"), list("value_2b")), list(list("value_3a"), list("value_3b"), list("value_3c")), list("value_4"))
for(i in 1:4) attr(before[[i]], "tag") <- paste0("tag_", i)
attr(before[[2]][[1]], "code") <- "code_2a"
attr(before[[2]][[2]], "code") <- "code_2b"
attr(before[[3]][[1]], "code") <- "code_3a"
attr(before[[3]][[2]], "code") <- "code_3b"
attr(before[[3]][[3]], "code") <- "code_3c"
str(before)
## List of 4
##  $ :List of 1
##   ..$ : chr "value_1"
##   ..- attr(*, "tag")= chr "tag_1"
##  $ :List of 2
##   ..$ :List of 1
##   .. ..$ : chr "value_2a"
##   .. ..- attr(*, "code")= chr "code_2a"
##   ..$ :List of 1
##   .. ..$ : chr "value_2b"
##   .. ..- attr(*, "code")= chr "code_2b"
##   ..- attr(*, "tag")= chr "tag_2"
##  $ :List of 3
##   ..$ :List of 1
##   .. ..$ : chr "value_3a"
##   .. ..- attr(*, "code")= chr "code_3a"
##   ..$ :List of 1
##   .. ..$ : chr "value_3b"
##   .. ..- attr(*, "code")= chr "code_3b"
##   ..$ :List of 1
##   .. ..$ : chr "value_3c"
##   .. ..- attr(*, "code")= chr "code_3c"
##   ..- attr(*, "tag")= chr "tag_3"
##  $ :List of 1
##   ..$ : chr "value_4"
##   ..- attr(*, "tag")= chr "tag_4"

(Note: 1st level list items have a "tag"-attribute, 2nd level items have a "code"-attribute.)

Should be this:

after <- list(tag_1="value_1", tag_2=list(code_2a="value_2a", code_2b="value_2b"), tag_3=list(code_3a="value_3a", code_3b="value_3b", code_3c="value_3c"), tag_4="value_4")
str(after)
## List of 4
##  $ tag_1: chr "value_1"
##  $ tag_2:List of 2
##   ..$ code_2a: chr "value_2a"
##   ..$ code_2b: chr "value_2b"
##  $ tag_3:List of 3
##   ..$ code_3a: chr "value_3a"
##   ..$ code_3b: chr "value_3b"
##   ..$ code_3c: chr "value_3c"
##  $ tag_4: chr "value_4"

Since the lists are large, I want to avoid for loops, to get a better performance.

Upvotes: 5

Views: 1978

Answers (2)

Stefan Jelkovich
Stefan Jelkovich

Reputation: 193

Got it! Three steps, but works perfectly.

# the ugly list
ugly_list <- list(list("value_1"), list(list("value_2a"), list("value_2b")), list(list("value_3a"), list("value_3b"), list("value_3c")), list("value_4"))
for(i in 1:4) attr(ugly_list[[i]], "tag") <- paste0("tag_", i)
attr(ugly_list[[2]][[1]], "code") <- "code_2a"
attr(ugly_list[[2]][[2]], "code") <- "code_2b"
attr(ugly_list[[3]][[1]], "code") <- "code_3a"
attr(ugly_list[[3]][[2]], "code") <- "code_3b"
attr(ugly_list[[3]][[3]], "code") <- "code_3c"

# set names for 1st level 
level_1_named <- setNames(ugly_list, sapply(ugly_list, function(x) attributes(x)$tag))

# set names for 2nd level
level_2_named <- lapply(level_1_named, function(x) lapply(x, function(y) setNames(y, attributes(y)$code)))

# clean list
clean_list <- lapply(level_2_named, function(x) unlist(x, recursive=FALSE))

Thanks for trying. :-)

Upvotes: 2

Omar Wagih
Omar Wagih

Reputation: 8732

You can easily do this by recursing through the list. Try this:

setListNames <- function(mylist){
  # Base case: if we have a nonlist object, set name to its attribute
  if( !is.list(mylist) ){
    names( mylist ) = attr(mylist, 'code')
    return( mylist )
  }

  # lapply through all sublists and recursively call
  mylist = lapply(mylist, setListNames)

  # Return named list
  return( mylist )
}

# Test run
before_named = setListNames(before)
# Check it worked
print( names( before_named[[2]][[1]][[1]] ) )

Upvotes: 1

Related Questions