How to recursively delete list items based on its nested content

57 views Asked by At

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.

1

There are 1 answers

0
MrFlick On BEST ANSWER

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.