1

I am using R to work with a large JS object (using the library rjsonio). As such, I have a lot of nested lists, which are getting somewhat cumbersome to work with. I have a simplified example below. I am trying to work with this object by creating some form of ‘getter’ and ‘setter’ functions. After looking around, I have found a pretty nice ‘getter’ function that recurses through the object and returns the first matching label. This is especially great because it lends itself to chaining functions together. However, I can not figure out a way to get the same effect for a ‘setter’ function. Any thoughts on how to create a ‘setter’ function that can be chained together in a similar fashion?

#example, simplified, object
app = list(
  1,
  2,
  d=list(a=123,
         b=456,
         list(
           FirstKey=list(attr1='good stuff', attr2=12345),
           SecondKey=list(attr1='also good stuff', attr2=4321)
           )
         )
  )


#Return a function that returns the value 
#associated with first label that matches 'name'
getByName <- function(name){
  rmatch <- function(x) {
    pos <- match(name, names(x))
    if (!is.na(pos))
      return(x[[pos]])
    for (el in x) {
      if (class(el) == "list") {
        out <- Recall(el)
        if (!is.null(out)) return(out)
      }
    }
  }
  rmatch
}

getFirstKey <- getByName("FirstKey")
getAttr1 <- getByName("attr1")
getAttr2 <- getByName("attr2")

#I like that I can chain these functions together
getAttr1(getFirstKey(app))
getAttr2(getFirstKey(app))

# I would like to be able to do something like this
# But this won't work
###    getAttr1(getFirstKey(app)) <- 9876

# This does work,,, but I loose the ability to chain functions together
# Closure around a replacement function
setterKeyAttr <- function(keyName, attr){
  function(x, value){
    x$d[[3]][[keyName]][[attr]] <- value
    x
  }
}

`setFirstKeyAttr2<-` <- setterKeyAttr("FirstKey", "attr2")
setFirstKeyAttr2(app) <- 22222
#check the answer is correct
getAttr2(getFirstKey(app))

references: R decorator to change both input and output

http://r.789695.n4.nabble.com/How-to-get-a-specific-named-element-in-a-nested-list-td3037430.html

http://adv-r.had.co.nz/Functions.html

Community
  • 1
  • 1
mgcdanny
  • 1,082
  • 1
  • 13
  • 20

1 Answers1

0

This is what I came up with. It makes the recursive function return the position of the 'name' and still be able to chain the calls together. I am not sure if this is a great way to do it... but it seems to be working... This is based off the fact that app[[c(3,3,1,)]] is a valid way to index in R.

 rmatch.pos <- function(object, name, seq=NA, level=NULL){
  ##return the vector of integers corresponding to the first match 
  ##of 'name' to a label in object or NULL if no match is found
    ###object: a list, likely deeply nested
    ##name: the name of the label to look for
    ##seq: starting point to search for 'name' in 'object' i.e. c(2,3,3)
    ##level: don't touch this; it keeps track of how deep the recursive execution is
  ##can be chained together to reduce ambiguity or result:
    ##obj <- list(a=1, b=list(c=2, d=list(e=1, attr1="really?", f=list(attr1 = "found me!"))))
    ##obj[[rmatch.pos(obj, "attr1", rmatch.pos(obj, "f"))]]

  if(is.null(seq)){
    #short circuit if NULL gets passed 
    #when chaining, this forces the whole 'chain'
    #to NULL when any 'link' is NULL
    return(NULL)
  }
  if(is.null(level)){
    level <- length(na.omit(seq))
  }
  if(any(is.na(seq))){
    temp <- object
  }else{
    temp <- object[[seq]]
  }
  level <- level + 1
  pos <- match(name, names(temp))
  if(!is.na(pos)){
    seq[level] <- pos
    return(seq)
  }
  for(el in seq_along(temp)){
    if(class(temp[[el]]) == "list"){
      seq[level] <- el
      out <- Recall(object, name, seq, level)
      if(!is.null(out)){
        return(out)
      }
    }
  }
}


###Examples
rmatch.pos(app, "ThirdKey")
rmatch.pos(app, "attr2")
###chaining example
rmatch.pos(app, "attr2", rmatch.pos(app, "FirstKey"))
rmatch.pos(app, "attr2", rmatch.pos(app, "SecondKey"))
rmatch.pos(app, "attr1", rmatch.pos(app, "ERROR"))
rmatch.pos(app, "ERROR", rmatch.pos(app, "attr1"))
mgcdanny
  • 1,082
  • 1
  • 13
  • 20