sebastian-c
sebastian-c

Reputation: 15415

Find empty lists in nested list of lists

Given an arbitrarily nested list, how can I find if a list contains empty lists? Consider the following example:

mylist <- list(list("foo", "bar", "baz", list(list())))

I tried rapply, but that skips through lists. While I could use lapply, I'd need to know the level of nesting beforehand. For this exercise, I don't need to know where the list is (although that would be a bonus), I just need a way to detect if there is one.

Upvotes: 10

Views: 1476

Answers (3)

Joris C.
Joris C.

Reputation: 6244

Another approach is to use rrapply in the rrapply-package (an extension of base-rrapply):

library(rrapply)

## check if any empty list exists
any(
  rrapply(mylist,
          classes = "list",
          condition = function(x) length(x) < 1,
          f = function(x) TRUE, 
          deflt = FALSE,
          how = "unlist"
  )
)
#> [1] TRUE

It is straightforward to update the above call to return the index vectors of any empty lists:

## return flat list with position vectors of empty list
rrapply(mylist,
        classes = "list",
        condition = function(x) length(x) < 1,
        f = function(x, .xpos) .xpos, 
        how = "flatten"
)
#> [[1]]
#> [1] 1 4 1

Here, we make use of the .xpos argument which evaluates to the position of the current list element under evaluation.


Note that this automatically returns all empty list positions instead of only one:

mylist2 <- list(list("foo", list(), "baz", list(list())))

rrapply(mylist2,
        classes = "list",
        condition = function(x) length(x) < 1,
        f = function(x, .xpos) .xpos, 
        how = "flatten"
)
#> [[1]]
#> [1] 1 2
#> 
#> [[2]]
#> [1] 1 4 1

## using MrFlick's find_empty_list function
find_empty_list(mylist2)
#> [1] 1 4 1

Upvotes: 1

MrFlick
MrFlick

Reputation: 206566

What about a function like this

has_empty_list <- function(x) {
    if(is.list(x)) {
        if (length(x)==0) {
            return(TRUE)
        } else {
            return(any(vapply(x, has_empty_list, logical(1))))
        }
    } else {
        return(FALSE)
    }
}

Basically we create a recursive function to look for lists of length 0.

has_empty_list( list(list("foo", "bar", "baz", list(list()))) )
# TRUE
has_empty_list( list(list("foo", "bar", "baz", list(list(4)))) )
# FALSE

And here's a modification to find the index of the empty list

find_empty_list <- function(x, index=c()) {
    if(is.list(x)) {
        #list
        if (length(x)==0) {
            if (length(index)==0) {
                return(0)
            } else {
                return(index)
            }
        } else {
            m <- Map(find_empty_list, x, lapply(seq_along(x), function(i) append(index,i)))
            # return the most deeply nested
            return( m[[which.max(lengths(m))]] )
        }
    } else {
        return(numeric())
    }
}

This should return a vector of the index that you can use to find the empty list. For example

( i <- find_empty_list(mylist) )
# [1] 1 4 1
mylist[[i]]
# list()

If the first parameter itself is an empty list, it will return 0

find_empty_list(list())
# 0

and if there is no empty list, it should return an empty vector

find_empty_list(list(1:3, list("c", a~b)))
# numeric()

Upvotes: 9

akuiper
akuiper

Reputation: 215117

Another convenient option to work with nested list is to use data.tree package:

library(data.tree)
nodes <- as.Node(mylist)
any(node$Get(function(node) length(as.list(node))) == 0)
# [1] TRUE

Upvotes: 5

Related Questions