Delete list items based on its nested content

81 views Asked by At

This is basically a spin-off of my earlier question here.

Taking into account the following example data, I'd like to recursively delete all children (and parents which have no more children) which contain the item selected = FALSE or vice versa copy all children which contain the item selected = TRUE while maintaining the list structure (please see the list expected_output).

My attempts so far fail:

# data --------------------------------------------------------------------
nodes <- list(
  list(
    text = "RootA",
    state = list(loaded = TRUE, opened = TRUE, selected = TRUE, disabled = FALSE),
    children = list(
      list(
        text = "ChildA1",
        state = list(loaded = TRUE, opened = TRUE, selected = TRUE, disabled = FALSE)
      ),
      list(
        text = "ChildA2",
        state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE)
      )
    )
  ),
  list(
    text = "RootB",
    state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE),
    children = list(
      list(
        text = "ChildB1",
        state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE)
      ),
      list(
        text = "ChildB2",
        state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE)
      )
    )
  )
)

# Error in x[[i]] : subscript out of bounds -------------------------------
delete_unselected_nodes <- function(x) {
  y <- list()
  for (i in seq_along(x)) {
    value <- x[[i]]
    if("state" %in% names(x)){
      if(x[["state"]][["selected"]] == TRUE) {
        y <- c(y, x[[i]]) 
      }
    } else {
      x[[i]] <- delete_unselected_nodes(value)
    }
  }
}

delete_unselected_nodes(nodes)

# expected output ---------------------------------------------------------
expected_output <- list(
  list(
    text = "RootA",
    state = list(loaded = TRUE, opened = TRUE, selected = TRUE, disabled = FALSE),
    children = list(
      list(
        text = "ChildA1",
        state = list(loaded = TRUE, opened = TRUE, selected = TRUE, disabled = FALSE)
      )
    )
  )
)
1

There are 1 answers

0
dufei On BEST ANSWER

I would use the functionality from purrr to deal with lists and define some (predicate) functions to keep it tidy:

library(tidyverse)
library(lobstr)

# define sample data ------------------------------------------------------

# ...

# define functions --------------------------------------------------------

# filter list of children to keep only where selected == TRUE
delete_unselected_children <- function(children_list) {
  keep(
    children_list,
    .p = \(x) x$state$selected)
}

# replace list element "children" of root with filtered list of children
delete_from_root <- function(root) {
  modify_in(
    root,
    .where = list("children"),
    .f = delete_unselected_children
  )
}

# define predicate function to drop roots without children
has_no_children <- function(root) {
  is_empty(root$children)
}


# apply -------------------------------------------------------------------

result <- nodes |> 
  map(delete_from_root) |> 
  discard(has_no_children)

lobstr::tree(result)
#> <list>
#> └─<list>
#>   ├─text: "RootA"
#>   ├─state: <list>
#>   │ ├─loaded: TRUE
#>   │ ├─opened: TRUE
#>   │ ├─selected: TRUE
#>   │ └─disabled: FALSE
#>   └─children: <list>
#>     └─<list>
#>       ├─text: "ChildA1"
#>       └─state: <list>
#>         ├─loaded: TRUE
#>         ├─opened: TRUE
#>         ├─selected: TRUE
#>         └─disabled: FALSE

Created on 2023-10-16 with reprex v2.0.2