slhck
slhck

Reputation: 38682

How to determine list-like objects in R, even when empty?

I have different possible input values, and I want to split them in two sets, one that contains single / atomar items, and one that contains multiple items or list-like structures that are empty.

For example, take these values:

123
"foo"
c()
c(c())
list()
list(list())
c(1, 2, 3)
c("a", "b", "c")
c(c("a", 1), "b", "c")
list("foo", "bar", c("baz", "blah"))

The first two should go into category A, the rest should go into category B.

I have tried various combinations of is.recursive or is.atomic, but I never get a correct split, as for example both 123 and c(1) are considered as atomic, numeric, and a vector of length 1.

I created an overview table of the different logical tests, but I can't seem to find something that distinguishes the first two lines from the others:

Am I missing something obvious here, like some property that'd help me distinguish those classes better?

code to reproduce the table:

(except categories)

types <- list(123, "foo", c(), c(c()), list(), list(list()), c(1, 2, 3), c("a", "b", "c"), c(c("a", 1), "b", "c"), list("foo", "bar", c("baz", "blah")))

data.frame(types = paste(types), 
           is.recursive = sapply(types, is.recursive), 
           is.atomic = sapply(types, is.atomic), 
           is.character = sapply(types, is.character), 
           is.numeric = sapply(types, is.numeric), 
           is.vector = sapply(types, is.vector), 
           is.list = sapply(types, is.list), 
           length = sapply(types, length)) 

Upvotes: 2

Views: 65

Answers (2)

slhck
slhck

Reputation: 38682

Based on the hint from @loki I found a solution that works for me:

if (is.null(x) || is.list(x) || (is.vector(x) && (length(x) > 1))) {
    print("Category B")
} else {
    print("Category A")
}

This now produces:

0                                     # => "Option A"
123                                   # => "Option A"
"foo"                                 # => "Option A"
c(1)                                  # => "Option A"
c()                                   # => "Option B"
c(c())                                # => "Option B"
list()                                # => "Option B"
list(list())                          # => "Option B"
c(1, 2, 3)                            # => "Option B"
c("a", "b", "c")                      # => "Option B"
c(c("a", 1), "b", "c")                # => "Option B"
list("foo", "bar", c("baz", "blah"))  # => "Option B"

It is worth noting that c(1) == 1, and reading the introduction to data structures helps, too.

Upvotes: 0

loki
loki

Reputation: 10350

You can use a combination of length(), is.vector() and is.null()

types <- list(123, "foo", c(), c(c()), list(), list(list()), c(1), c(1, 2, 3), c("a", "b", "c"), c(c("a", 1), "b", "c"), list("foo", "bar", c("baz", "blah")))
data.frame(types = paste(types), 
           is.recursive = sapply(types, is.recursive), 
           is.atomic = sapply(types, is.atomic), 
           is.character = sapply(types, is.character), 
           is.numeric = sapply(types, is.numeric), 
           is.vector = sapply(types, is.vector), 
           is.list = sapply(types, is.list), 
           length = sapply(types, length), 
           is.null = sapply(types, is.null), 
           typeof = sapply(types, typeof), 
           class = sapply(types, class), 

           # and now let's get to the mystery using 4 of these values:
           category = sapply(types, function(x){
             ifelse(is.null(x) || is.list(x) || (is.vector(x) && length(x) > 1), "B", "A")
           })) 
#                                  types ... category
#1                                   123 ...        A
#2                                   foo ...        A
#3                                   c() ...        B
#4                                c(c()) ...        B
#5                                list() ...        B
#6                          list(list()) ...        B
#7                                  c(1) ...        A
#8                            c(1, 2, 3) ...        B
#9                      c("a", "b", "c") ...        B
#10                c("a", "1", "b", "c") ...        B
#11 list("foo", "bar", c("baz", "blah")) ...        B

Furthermore, you should have a look at rapportools::is.empty(). However, it fails for nested lists (list(list())).

Upvotes: 1

Related Questions