Search code examples
rrecursionpurrrjstreer

How to recursively delete list items based on its nested content


I'm trying to recursively delete list items based on their nested content (delete if checked = TRUE).

I recently asked a similar question here. However, I just realized that the given answer doesn't work recursively.

Here is my example data:

library(jsTreeR)
library(jsonlite)

nodes <- list(
  list(
    text = "Branch 1",
    state = list(
      opened = TRUE,
      disabled = FALSE,
      selected = FALSE
    ),
    type = "parent",
    children = list(
      list(
        text = "Leaf A",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf B",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf C",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = TRUE
        ),
        type = "child"
      ),
      list(
        text = "Leaf D",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE
        ),
        type = "child",
        children = list(
          list(
            text = "Leaf D1",
            state = list(
              opened = TRUE,
              disabled = FALSE,
              selected = FALSE,
              checked = FALSE
            ),
            type = "child"
          ),
          list(
            text = "Leaf D2",
            state = list(
              opened = TRUE,
              disabled = FALSE,
              selected = FALSE,
              checked = TRUE
            ),
            type = "child"
          )
        )
      )
    )
  ),
  list(
    text = "Branch 2",
    type = "parent",
    state = list(
      opened = TRUE,
      disabled = FALSE,
      selected = FALSE,
      checked = FALSE
    )
  )
)


jstree(nodes, checkboxes = TRUE, checkWithText = FALSE)
# toJSON(nodes, pretty = TRUE)

I tried using base R and {rrapply}, but can't get it to work properly:

delete_unchecked <- function(x) {
  for (i in seq_along(x)) {
    value <- x[[i]]
    if (is.list(value)) {
      x[[i]] <- delete_unchecked(value)
    } else {
      if("state" %in% names(x) && isTRUE(x$state$checked)){x[[i]] <- NULL}
    }
  }
  x
}

library(rrapply)
result <- rrapply(nodes, condition = function(x){
  if("state" %in% names(x)){
    isTRUE(x$state$checked)
  } else {TRUE}
}, how = "prune")

This is my expected output:

result

expected_output <- list(
  list(
    text = "Branch 1",
    state = list(
      opened = TRUE,
      disabled = FALSE,
      selected = FALSE
    ),
    type = "parent",
    children = list(
      list(
        text = "Leaf A",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf B",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf D",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE
        ),
        type = "child",
        children = list(
          list(
            text = "Leaf D1",
            state = list(
              opened = TRUE,
              disabled = FALSE,
              selected = FALSE,
              checked = FALSE
            ),
            type = "child"
          )
        )
      )
    )
  ),
  list(
    text = "Branch 2",
    type = "parent",
    state = list(
      opened = TRUE,
      disabled = FALSE,
      selected = FALSE,
      checked = FALSE
    )
  )
)

PS: I'd prefer base R answers - however, open to other suggestions.


Solution

  • You can use some recursion to check each node and it's children

    remove_checked <- function(x) {
      drop_null <- function(x) Filter(Negate(is.null), x)
      is_checked <- function(x) "state" %in% names(x) && "checked" %in% names(x$state) && x$state$checked
      drop <- function(x) {
        if (is_checked(x)) return(NULL)
        if ("children" %in% names(x)) {
          x$children <- drop_null(lapply(x$children, drop))
        }
        x
      }
      drop_null(lapply(x, drop))
    }
    result <- remove_checked(nodes)
    

    We swap out the checked ones with NULL and then remove NULL from the list.