7

Is there a "built-in"/efficient and robust way to check if list objects are nested or not?

To clarify my understanding of the term nested:

Flat or not-nested list

x.1 <- list(
    a=TRUE, 
    b=1:5
)

Nested list

x.2 <- list(
    a=list(a.1=list(a.1.1=TRUE)), 
    b=list(b.1=1:5)
)

My first idea was to use a combination of str, capture.output and regular expressions. But as everything related to regular expression: pretty powerful, pretty risky on the robustness side ;-) So I wondered if there's something better out there:

isNested <- function(x) {
    if (class(x) != "list") {
        stop("Expecting 'x' to be a list")
    }
    out <- FALSE
    strout <- capture.output(str(x))
    idx <- grep("\\$.*List", strout)
    if (length(idx)) {
        out <- TRUE
    }
    return(out)
}

> isNested(x=x.1)
[1] FALSE
> isNested(x=x.2)
[1] TRUE

Second approach courtesy of Roman and Arun:

isNested2 <- function(x) {
    if (class(x) != "list") {
        stop("Expecting 'x' to be a list")
    }
    out <- any(sapply(x, is.list))
    return(out)
}

> isNested2(x=x.1)
[1] FALSE
> isNested2(x=x.2)
[1] TRUE
Rappster
  • 12,762
  • 7
  • 71
  • 120
  • 1
    What if you checked your first order list if class is list? If yes, it's nested, else not. Something along the lines of `any(sapply(x.2, function(x) class(x) == "list"))`. `any(sapply(x.1, function(x) class(x) == "list"))` returns FALSE. – Roman Luštrik Mar 13 '13 at 10:06
  • 1
    `any(sapply(my_list, class) == "list")` – Arun Mar 13 '13 at 10:07
  • Right, that would have been easier ;-) Thanks guys! Embedded your approach as a second approach. Cheers – Rappster Mar 13 '13 at 10:10
  • 2
    For bonus points (how deeply nested is the list), see @Spacedman's answer here: http://stackoverflow.com/a/13433689/1270695 – A5C1D2H2I1M1N2O1R2T1 Mar 13 '13 at 10:13
  • @Rappster, `any(.)` already provides `TRUE/FALSE`. You don't have to wrap it around an `if-statement (or assign out to FALSE).. – Arun Mar 13 '13 at 10:20

4 Answers4

13

You can use the is.list function:

any(sapply(x.1, is.list))
[1] FALSE

any(sapply(x.2, is.list))
[1] TRUE

As a function isNested:

isNested <- function(l) {
  stopifnot(is.list(l))
  for (i in l) {
    if (is.list(i)) return(TRUE)
  }
  return(FALSE)
}

Instead of testing all list elements, the function stops as soon as it detects a nested list.

Sven Hohenstein
  • 80,497
  • 17
  • 145
  • 168
4

Try this :

   isNested <- function(x) {
    if (is.list(x))
        stop("Expecting 'x' to be a list")

    any(unlist( lapply(x,is.list) ))
   }
Pop
  • 12,135
  • 5
  • 55
  • 68
  • Thanks for answering and putting it into "my function context"! Gave it to Sven though because it's just a tiny bit more concise. Cheers – Rappster Mar 13 '13 at 10:25
3

Here's another way for the fun of it:

length(unlist(l, FALSE)) != length(unlist(l))  

Or a variation on that:

!identical(unlist(l, FALSE), unlist(l))

Makes use of the recursive parameter of unlist(). With the error checking as well if you want:

isNested <- function(l) {
  if (!is.list(l)) stop("Not a list.")
  !identical(unlist(l, FALSE), unlist(l))  
}
Ciarán Tobin
  • 7,306
  • 1
  • 29
  • 45
0

What if the contents of the list are S3 objects which in turn have a nested list? I'd want these not to be treated as lists, so that a list of these objects is "not nested" (it's just a list of objects rather than a list of lists). Using class() rather than is.list() so that check if literally list rather than something else with an embedded list.

is.nested <- function(x) {
  stopifnot(is.list(x))
  any(sapply(x, function(x) any(class(x) == "list")))
}
datawookie
  • 1,607
  • 12
  • 20